import XCTest

/// Extensiones y configuraciones adicionales para XCTest
/// Proporciona funcionalidades adicionales para los tests

extension XCTestCase {

    /// Espera un periodo de tiempo específico (útil para operaciones asíncronas)
    func wait(for duration: TimeInterval) async {
        try? await Task.sleep(nanoseconds: UInt64(duration * 1_000_000_000))
    }

    /// Ejecuta una operación y espera que complete
    func waitForOperation<T>(
        timeout: TimeInterval = 5.0,
        operation: @escaping () -> T?,
        file: StaticString = #file,
        line: UInt = #line
    ) -> T? {
        let expectation = self.expectation(description: "Operation completed")

        var result: T?

        DispatchQueue.global().async {
            result = operation()
            expectation.fulfill()
        }

        waitForExpectations(timeout: timeout) { error in
            if let error = error {
                XCTFail("Operation timed out: \(error.localizedDescription)", file: file, line: line)
            }
        }

        return result
    }

    /// Verifica que una operación lanza un error específico
    func assertThrowsError<T>(
        _ error: Error,
        in expression: () throws -> T,
        file: StaticString = #file,
        line: UInt = #line
    ) {
        XCTAssertThrowsError(
            try expression(),
            file: file,
            line: line
        ) { thrownError in
            XCTAssertEqual(
                thrownError as? Error,
                error,
                "Expected error does not match thrown error",
                file: file,
                line: line
            )
        }
    }

    /// Ejecuta un test multiple veces para detectar fallos intermitentes
    func repeatTest(
        _ count: Int = 10,
        file: StaticString = #file,
        line: UInt = #line,
        test: () throws -> Void
    ) {
        var failures = 0

        for iteration in 1...count {
            do {
                try test()
            } catch {
                failures += 1
                print("Test failed on iteration \(iteration): \(error)")
            }
        }

        XCTAssertEqual(
            failures,
            0,
            "Test failed \(failures) out of \(count) times",
            file: file,
            line: line
        )
    }
}

// MARK: - Custom Assertions

extension XCTestCase {

    /// Afirma que un closure no lanza error
    func assertNoThrow(
        _ expression: () throws -> Void,
        file: StaticString = #file,
        line: UInt = #line
    ) {
        do {
            try expression()
        } catch {
            XCTFail(
                "Unexpected error thrown: \(error.localizedDescription)",
                file: file,
                line: line
            )
        }
    }

    /// Afirma que dos fechas son aproximadamente iguales (dentro de un margen)
    func assertDatesEqual(
        _ date1: Date,
        _ date2: Date,
        precision: TimeInterval = 0.001,
        file: StaticString = #file,
        line: UInt = #line
    ) {
        let difference = abs(date1.timeIntervalSince(date2))
        XCTAssertLessThanOrEqual(
            difference,
            precision,
            "Dates are not equal within \(precision)s",
            file: file,
            line: line
        )
    }

    /// Afirma que un array contiene un número específico de elementos
    func assertCount(
        _ count: Int,
        _ array: [Any],
        file: StaticString = #file,
        line: UInt = #line
    ) {
        XCTAssertEqual(
            array.count,
            count,
            "Array count mismatch",
            file: file,
            line: line
        )
    }

    /// Afirma que una colección está vacía
    func assertEmpty<T>(
        _ collection: T,
        file: StaticString = #file,
        line: UInt = #line
    ) where T: Collection {
        XCTAssertTrue(
            collection.isEmpty,
            "Collection should be empty but has \(collection.count) elements",
            file: file,
            line: line
        )
    }

    /// Afirma que una colección no está vacía
    func assertNotEmpty<T>(
        _ collection: T,
        file: StaticString = #file,
        line: UInt = #line
    ) where T: Collection {
        XCTAssertFalse(
            collection.isEmpty,
            "Collection should not be empty",
            file: file,
            line: line
        )
    }
}

// MARK: - Memory Leak Detection

extension XCTestCase {

    /// Detecta memory leaks en un objeto
    func assertNoMemoryLeak(
        _ instance: AnyObject,
        file: StaticString = #file,
        line: UInt = #line
    ) {
        addTeardownBlock { [weak instance] in
            XCTAssertNil(
                instance,
                "Instance should be deallocated but still exists (potential memory leak)",
                file: file,
                line: line
            )
        }
    }
}

// MARK: - Test Logging

extension XCTestCase {

    /// Registra información de depuración durante los tests
    func logTest(_ message: String, level: LogLevel = .info) {
        let prefix = "[Test \(level.description)]"
        print("\(prefix) \(message)")

        #if DEBUG
        let testRun = XCTRunLoop.current.currentTestRun
        print("Test: \(testRun?.test.name ?? "Unknown") - \(message)")
        #endif
    }

    enum LogLevel {
        case info
        case warning
        case error

        var description: String {
            switch self {
            case .info: return "INFO"
            case .warning: return "WARNING"
            case .error: return "ERROR"
            }
        }
    }
}

// MARK: - Test Data Cleanup

extension XCTestCase {

    /// Limpia todos los UserDefaults
    func clearAllUserDefaults() {
        let dictionary = UserDefaults.standard.dictionaryRepresentation()
        dictionary.keys.forEach { key in
            UserDefaults.standard.removeObject(forKey: key)
        }
    }

    /// Limpia todos los archivos en el directorio temporal
    func clearTemporaryDirectory() {
        let tempDir = FileManager.default.temporaryDirectory
        guard let contents = try? FileManager.default.contentsOfDirectory(
            at: tempDir,
            includingPropertiesForKeys: nil
        ) else { return }

        for file in contents {
            try? FileManager.default.removeItem(at: file)
        }
    }
}

// MARK: - Custom Test Runners

/// Configuración para ejecutar todos los tests
class AllTests {
    static func runAllTests() {
        print("🧪 Running MangaReader Test Suite")
        print("=" * 50)

        // Tests de Modelos
        print("📦 Running Model Tests...")
        // ModelTests se ejecutan automáticamente

        // Tests de Storage
        print("💾 Running Storage Service Tests...")
        // StorageServiceTests se ejecutan automáticamente

        // Tests de Scraper
        print("🌐 Running Scraper Tests...")
        // ManhwaWebScraperTests se ejecutan automáticamente

        // Tests de Integración
        print("🔗 Running Integration Tests...")
        // IntegrationTests se ejecutan automáticamente

        print("=" * 50)
        print("✅ All tests completed!")
    }
}

// MARK: - Test Metrics

extension XCTestCase {

    /// Registra métricas de rendimiento
    func recordMetric(_ name: String, value: Double, unit: String = "s") {
        #if DEBUG
        let metric = [
            "name": name,
            "value": value,
            "unit": unit,
            "timestamp": Date().timeIntervalSince1970
        ] as [String : Any]

        print("📊 Metric: \(name) = \(value) \(unit)")
        #endif
    }

    /// Compara métricas entre runs
    func assertMetricImproved(
        _ name: String,
        currentValue: Double,
        previousValue: Double,
        file: StaticString = #file,
        line: UInt = #line
    ) {
        let improvement = ((previousValue - currentValue) / previousValue) * 100

        XCTAssertGreaterThan(
            improvement,
            0,
            "Metric '\(name)' did not improve. Previous: \(previousValue), Current: \(currentValue)",
            file: file,
            line: line
        )

        print("✨ Metric '\(name)' improved by \(String(format: "%.2f", improvement))%")
    }
}

// MARK: - String Repetition Helper

extension String {
    static func * (left: String, right: Int) -> String {
        guard right > 0 else { return "" }
        return String(repeating: left, count: right)
    }
}

// MARK: - Test Documentation

/*
 Guía de Ejecución de Tests:

 1. Ejecutar todos los tests:
    - Cmd + U en Xcode
    - O seleccionar Product > Test

 2. Ejecutar tests específicos:
    - Click derecho en el test específico > Run
    - Usar el Test Navigator (Cmd + 6)

 3. Ejecutar tests con cobertura:
    - Edit Scheme > Test > Options > Gather coverage
    - Cmd + U

 4. Tests Performance:
    - Los tests de performance se marcan con "measure"
    - Se ejecutan 10 veces por defecto
    - Resultados en el Report Navigator

 Estructura de Tests:

 - ModelTests: Pruebas para modelos de datos (Manga, Chapter, etc.)
 - StorageServiceTests: Pruebas para almacenamiento local
 - ManhwaWebScraperTests: Pruebas para el web scraper
 - IntegrationTests: Pruebas de integración completa

 Mejores Prácticas:

 1. Cada test debe ser independiente
 2. Los tests deben poder ejecutarse en cualquier orden
 3. Usar setUp/tearDown para limpieza
 4. Usar nombres descriptivos para los tests
 5. Un assert por test (cuando sea posible)
 6. Mock de dependencias externas
 7. Evitar llamadas de red reales en tests unitarios

 Marcas de Tests:

 - @MainActor: Tests que requieren MainActor
 - async: Tests asíncronos
 - throws: Tests que pueden lanzar errores
 */

// MARK: - Custom Test Constraints

#if DEBUG

/// Contenedor para configuración de tests
struct TestConfiguration {
    static var isRunningTests: Bool {
        return NSClassFromString("XCTest") != nil
    }

    static var testTimeout: TimeInterval = 10.0
    static var useMockData: Bool = true
    static var verboseLogging: Bool = true
}

#endif
