import { describe, it, expect, beforeEach, afterEach } from "vitest";
import type { SourceProvider } from "../interfaces/SourceProvider";
import type { IReactiveFileSystem } from "@firesystem/core";

/**
 * Test suite genérica para providers do workspace
 * Garante que todos os providers implementam corretamente a interface
 */
export function createProviderTestSuite(
  providerName: string,
  createProvider: () => SourceProvider,
  validConfig?: any,
  invalidConfigs?: Array<{ config: any; expectedError: string }>
) {
  describe(`${providerName} Provider Test Suite`, () => {
    let provider: SourceProvider;
    let filesystems: IReactiveFileSystem[] = [];

    beforeEach(() => {
      provider = createProvider();
      filesystems = [];
    });

    afterEach(async () => {
      // Limpar todos os file systems criados
      for (const fs of filesystems) {
        if (provider.dispose) {
          await provider.dispose!(fs);
        }
      }
      filesystems = [];
    });

    describe("Provider Interface", () => {
      it("should have required properties", () => {
        expect(provider.scheme).toBeDefined();
        expect(typeof provider.scheme).toBe("string");
        expect(provider.scheme.length).toBeGreaterThan(0);

        expect(provider.displayName).toBeDefined();
        expect(typeof provider.displayName).toBe("string");
        expect(provider.displayName.length).toBeGreaterThan(0);
      });

      it("should have required methods", () => {
        expect(typeof provider.createFileSystem).toBe("function");
        expect(typeof provider.getCapabilities).toBe("function");
      });
    });

    describe("createFileSystem", () => {
      it("should create a file system without config", async () => {
        const fs = await provider.createFileSystem({});
        filesystems.push(fs);

        expect(fs).toBeDefined();
        expect(fs.readFile).toBeDefined();
        expect(fs.writeFile).toBeDefined();
        expect(fs.deleteFile).toBeDefined();
        expect(fs.exists).toBeDefined();
        expect(fs.mkdir).toBeDefined();
        expect(fs.readDir).toBeDefined();
      });

      if (validConfig) {
        it("should create a file system with valid config", async () => {
          const fs = await provider.createFileSystem(validConfig);
          filesystems.push(fs);

          expect(fs).toBeDefined();
          
          // Testar operação básica
          await fs.writeFile("/test.txt", "Hello Provider");
          const file = await fs.readFile("/test.txt");
          expect(file.content).toBe("Hello Provider");
        });
      }

      it("should create multiple independent file systems", async () => {
        const fs1 = await provider.createFileSystem({});
        const fs2 = await provider.createFileSystem({});
        filesystems.push(fs1, fs2);

        // Escrever em fs1
        await fs1.writeFile("/file1.txt", "FS1 Content");
        
        // Verificar que fs2 não tem o arquivo
        expect(await fs2.exists("/file1.txt")).toBe(false);
        
        // Escrever em fs2
        await fs2.writeFile("/file2.txt", "FS2 Content");
        
        // Verificar independência
        expect(await fs1.exists("/file2.txt")).toBe(false);
        expect(await fs2.exists("/file1.txt")).toBe(false);
      });
    });

    describe("validateConfiguration", () => {
      if (provider.validateConfiguration) {
        it("should validate empty config", async () => {
          const result = await provider.validateConfiguration!({});
          expect(result).toBeDefined();
          expect(typeof result.valid).toBe("boolean");
        });

        if (validConfig) {
          it("should accept valid config", async () => {
            const result = await provider.validateConfiguration!(validConfig);
            expect(result.valid).toBe(true);
            expect(result.errors).toBeUndefined();
          });
        }

        if (invalidConfigs) {
          invalidConfigs.forEach(({ config, expectedError }, index) => {
            it(`should reject invalid config #${index + 1}`, async () => {
              const result = await provider.validateConfiguration!(config);
              expect(result.valid).toBe(false);
              expect(result.errors).toBeDefined();
              expect(result.errors!.some(e => e.includes(expectedError))).toBe(true);
            });
          });
        }
      }
    });

    describe("getCapabilities", () => {
      it("should return valid capabilities", () => {
        const capabilities = provider.getCapabilities();

        expect(capabilities).toBeDefined();
        expect(typeof capabilities.readonly).toBe("boolean");
        expect(typeof capabilities.caseSensitive).toBe("boolean");
        expect(typeof capabilities.atomicRename).toBe("boolean");
        expect(typeof capabilities.supportsWatch).toBe("boolean");
        expect(typeof capabilities.supportsMetadata).toBe("boolean");

        if (capabilities.maxFileSize !== undefined) {
          expect(typeof capabilities.maxFileSize).toBe("number");
          expect(capabilities.maxFileSize).toBeGreaterThan(0);
        }

        if (capabilities.maxPathLength !== undefined) {
          expect(typeof capabilities.maxPathLength).toBe("number");
          expect(capabilities.maxPathLength).toBeGreaterThan(0);
        }
      });
    });

    describe("dispose", () => {
      if (provider.dispose) {
        it("should properly dispose file system", async () => {
          const fs = await provider.createFileSystem({});
          
          // Criar alguns arquivos
          await fs.writeFile("/test1.txt", "Content 1");
          await fs.writeFile("/test2.txt", "Content 2");
          await fs.mkdir("/folder");
          
          // Verificar que existem
          expect(await fs.exists("/test1.txt")).toBe(true);
          expect(await fs.exists("/test2.txt")).toBe(true);
          expect(await fs.exists("/folder")).toBe(true);
          
          // Dispose
          await provider.dispose!(fs);
          
          // Comportamento após dispose pode variar por provider
          // Alguns podem limpar, outros podem desconectar
          // Não assumimos comportamento específico aqui
        });
      }
    });

    describe("FileSystem Operations", () => {
      it("should support basic file operations", async () => {
        const fs = await provider.createFileSystem({});
        filesystems.push(fs);

        // Write
        await fs.writeFile("/hello.txt", "Hello World");
        
        // Read
        const file = await fs.readFile("/hello.txt");
        expect(file.content).toBe("Hello World");
        expect(file.type).toBe("file");
        
        // Exists
        expect(await fs.exists("/hello.txt")).toBe(true);
        expect(await fs.exists("/missing.txt")).toBe(false);
        
        // Delete
        await fs.deleteFile("/hello.txt");
        expect(await fs.exists("/hello.txt")).toBe(false);
      });

      it("should support directory operations", async () => {
        const fs = await provider.createFileSystem({});
        filesystems.push(fs);

        // Create directory
        await fs.mkdir("/folder");
        expect(await fs.exists("/folder")).toBe(true);
        
        // Create nested structure
        await fs.mkdir("/folder/subfolder", true);
        await fs.writeFile("/folder/file.txt", "In folder");
        await fs.writeFile("/folder/subfolder/nested.txt", "Nested");
        
        // Read directory
        const entries = await fs.readDir("/folder");
        expect(entries.length).toBe(2);
        expect(entries.some(e => e.name === "subfolder")).toBe(true);
        expect(entries.some(e => e.name === "file.txt")).toBe(true);
        
        // Remove directory
        await fs.rmdir("/folder/subfolder", true);
        expect(await fs.exists("/folder/subfolder")).toBe(false);
      });

      it("should support glob patterns", async () => {
        const fs = await provider.createFileSystem({});
        filesystems.push(fs);

        // Create test structure
        await fs.writeFile("/file1.txt", "1");
        await fs.writeFile("/file2.js", "2");
        await fs.mkdir("/folder");
        await fs.writeFile("/folder/file3.txt", "3");
        await fs.writeFile("/folder/file4.js", "4");
        
        // Test patterns
        const txtFiles = await fs.glob("**/*.txt");
        expect(txtFiles.sort()).toEqual(["/file1.txt", "/folder/file3.txt"]);
        
        const jsFiles = await fs.glob("**/*.js");
        expect(jsFiles.sort()).toEqual(["/file2.js", "/folder/file4.js"]);
        
        const allFiles = await fs.glob("**/*");
        expect(allFiles.length).toBe(5); // 4 files + 1 directory
      });
    });

    describe("Events (if supported)", () => {
      it("should emit events if reactive", async () => {
        const fs = await provider.createFileSystem({});
        filesystems.push(fs);

        if (fs.events) {
          const events: string[] = [];
          
          fs.events.on("file:written", ({ path }) => {
            events.push(`written:${path}`);
          });
          
          fs.events.on("file:deleted", ({ path }) => {
            events.push(`deleted:${path}`);
          });
          
          await fs.writeFile("/test.txt", "content");
          await fs.deleteFile("/test.txt");
          
          // Dar tempo para eventos serem processados
          await new Promise(resolve => setTimeout(resolve, 10));
          
          expect(events).toContain("written:/test.txt");
          expect(events).toContain("deleted:/test.txt");
        }
      });
    });
  });
}

/**
 * Helper para criar testes de provider com configurações mínimas
 */
export function testProvider(provider: SourceProvider) {
  return createProviderTestSuite(
    provider.displayName,
    () => provider
  );
}