import { describe, expect, test } from 'vitest'
import { validateRelativePath } from '../../../src/template/utils'
import { TemplateError } from '../../../src/errors'

const isWindows = process.platform === 'win32'

describe('validateRelativePath', () => {
  describe('valid paths', () => {
    test('accepts simple relative path', () => {
      expect(() => validateRelativePath('foo', undefined)).not.toThrow()
    })

    test('accepts nested relative path', () => {
      expect(() => validateRelativePath('foo/bar', undefined)).not.toThrow()
    })

    test('accepts path with ./ prefix', () => {
      expect(() => validateRelativePath('./foo', undefined)).not.toThrow()
    })

    test('accepts nested path with ./ prefix', () => {
      expect(() => validateRelativePath('./foo/bar', undefined)).not.toThrow()
    })

    test('accepts path with internal parent ref that stays within context', () => {
      expect(() => validateRelativePath('foo/../bar', undefined)).not.toThrow()
    })

    test('accepts current directory', () => {
      expect(() => validateRelativePath('.', undefined)).not.toThrow()
    })

    test('accepts glob patterns', () => {
      expect(() => validateRelativePath('*.txt', undefined)).not.toThrow()
      expect(() => validateRelativePath('**/*.ts', undefined)).not.toThrow()
      expect(() => validateRelativePath('src/**/*', undefined)).not.toThrow()
    })

    test('accepts hidden files and directories', () => {
      expect(() => validateRelativePath('.hidden', undefined)).not.toThrow()
      expect(() =>
        validateRelativePath('.config/settings', undefined)
      ).not.toThrow()
    })

    test('accepts filenames starting with double dots', () => {
      expect(() => validateRelativePath('..myconfig', undefined)).not.toThrow()
      expect(() => validateRelativePath('..cache', undefined)).not.toThrow()
      expect(() =>
        validateRelativePath('...something', undefined)
      ).not.toThrow()
      expect(() =>
        validateRelativePath('foo/..myconfig', undefined)
      ).not.toThrow()
    })
  })

  describe('invalid paths - absolute', () => {
    test('rejects Unix absolute path', () => {
      expect(() => validateRelativePath('/absolute/path', undefined)).toThrow(
        TemplateError
      )
      expect(() => validateRelativePath('/absolute/path', undefined)).toThrow(
        'absolute paths are not allowed'
      )
    })

    test('rejects root path', () => {
      expect(() => validateRelativePath('/', undefined)).toThrow(TemplateError)
    })

    // Windows path tests - only run on Windows where path.isAbsolute detects them
    test.skipIf(!isWindows)('rejects Windows drive letter path', () => {
      expect(() =>
        validateRelativePath('C:\\Windows\\System32', undefined)
      ).toThrow(TemplateError)
      expect(() =>
        validateRelativePath('C:\\Windows\\System32', undefined)
      ).toThrow('absolute paths are not allowed')
    })

    test.skipIf(!isWindows)('rejects Windows UNC path', () => {
      expect(() =>
        validateRelativePath('\\\\server\\share', undefined)
      ).toThrow(TemplateError)
    })
  })

  describe('invalid paths - parent directory escape', () => {
    test('rejects simple parent directory escape', () => {
      expect(() => validateRelativePath('../foo', undefined)).toThrow(
        TemplateError
      )
      expect(() => validateRelativePath('../foo', undefined)).toThrow(
        'path escapes the context directory'
      )
    })

    test('rejects parent directory escape with forward slash', () => {
      expect(() => validateRelativePath('../file.txt', undefined)).toThrow(
        TemplateError
      )
    })

    test.skipIf(!isWindows)(
      'rejects parent directory escape with backslash',
      () => {
        expect(() => validateRelativePath('..\\file.txt', undefined)).toThrow(
          TemplateError
        )
      }
    )

    test('rejects double parent directory escape', () => {
      expect(() => validateRelativePath('../../foo', undefined)).toThrow(
        TemplateError
      )
    })

    test('rejects path that escapes via nested parent refs', () => {
      expect(() => validateRelativePath('foo/../../bar', undefined)).toThrow(
        TemplateError
      )
    })

    test('rejects path with ./ prefix that escapes', () => {
      expect(() =>
        validateRelativePath('./foo/../../../bar', undefined)
      ).toThrow(TemplateError)
    })

    test('rejects just parent directory', () => {
      expect(() => validateRelativePath('..', undefined)).toThrow(TemplateError)
    })

    test('rejects current directory followed by parent', () => {
      expect(() => validateRelativePath('./..', undefined)).toThrow(
        TemplateError
      )
    })

    test('rejects deeply nested escape', () => {
      expect(() =>
        validateRelativePath('a/b/c/../../../../escape', undefined)
      ).toThrow(TemplateError)
    })
  })

  describe('error messages include path', () => {
    test('absolute path error includes the path', () => {
      try {
        validateRelativePath('/etc/passwd', undefined)
        expect.fail('Should have thrown')
      } catch (e) {
        expect(e.message).toContain('/etc/passwd')
      }
    })

    test('escape path error includes the path', () => {
      try {
        validateRelativePath('../secret', undefined)
        expect.fail('Should have thrown')
      } catch (e) {
        expect(e.message).toContain('../secret')
      }
    })
  })
})
