import XCTest
import UIKit
@testable import MangaReader

/// Helpers y mocks para los tests
/// Proporciona métodos de utilidad para crear datos de prueba y configurar tests

// MARK: - Test Data Factory

class TestDataFactory {

    /// Crea un manga de prueba
    static func createManga(
        slug: String = "test-manga",
        title: String = "Test Manga",
        description: String = "A test manga description",
        genres: [String] = ["Action", "Adventure"],
        status: String = "PUBLICANDOSE",
        url: String = "https://manhwaweb.com/manga/test",
        coverImage: String? = nil
    ) -> Manga {
        return Manga(
            slug: slug,
            title: title,
            description: description,
            genres: genres,
            status: status,
            url: url,
            coverImage: coverImage
        )
    }

    /// Crea un capítulo de prueba
    static func createChapter(
        number: Int = 1,
        title: String = "Chapter 1",
        url: String = "https://manhwaweb.com/leer/test/1",
        slug: String = "test/1",
        isRead: Bool = false,
        isDownloaded: Bool = false,
        lastReadPage: Int = 0
    ) -> Chapter {
        var chapter = Chapter(number: number, title: title, url: url, slug: slug)
        chapter.isRead = isRead
        chapter.isDownloaded = isDownloaded
        chapter.lastReadPage = lastReadPage
        return chapter
    }

    /// Crea una página de manga de prueba
    static func createMangaPage(
        url: String = "https://example.com/page.jpg",
        index: Int = 0,
        isCached: Bool = false
    ) -> MangaPage {
        return MangaPage(url: url, index: index, isCached: isCached)
    }

    /// Crea un progreso de lectura de prueba
    static func createReadingProgress(
        mangaSlug: String = "test-manga",
        chapterNumber: Int = 1,
        pageNumber: Int = 0,
        timestamp: Date = Date()
    ) -> ReadingProgress {
        return ReadingProgress(
            mangaSlug: mangaSlug,
            chapterNumber: chapterNumber,
            pageNumber: pageNumber,
            timestamp: timestamp
        )
    }

    /// Crea un capítulo descargado de prueba
    static func createDownloadedChapter(
        mangaSlug: String = "test-manga",
        mangaTitle: String = "Test Manga",
        chapterNumber: Int = 1,
        pages: [MangaPage] = [],
        downloadedAt: Date = Date(),
        totalSize: Int64 = 0
    ) -> DownloadedChapter {
        return DownloadedChapter(
            mangaSlug: mangaSlug,
            mangaTitle: mangaTitle,
            chapterNumber: chapterNumber,
            pages: pages,
            downloadedAt: downloadedAt,
            totalSize: totalSize
        )
    }

    /// Crea múltiples capítulos de prueba
    static func createChapters(count: Int, startingFrom: Int = 1) -> [Chapter] {
        return (startingFrom..<(startingFrom + count)).map { i in
            createChapter(
                number: i,
                title: "Chapter \(i)",
                url: "https://manhwaweb.com/leer/test/\(i)",
                slug: "test/\(i)"
            )
        }
    }

    /// Crea múltiples páginas de prueba
    static func createPages(count: Int) -> [MangaPage] {
        return (0..<count).map { i in
            createMangaPage(
                url: "https://example.com/page\(i).jpg",
                index: i
            )
        }
    }
}

// MARK: - Image Test Helpers

class ImageTestHelpers {

    /// Crea una imagen de prueba con un color específico
    static func createTestImage(
        color: UIColor = .blue,
        size: CGSize = CGSize(width: 800, height: 1200)
    ) -> UIImage {
        let renderer = UIGraphicsImageRenderer(size: size)
        return renderer.image { context in
            color.setFill()
            context.fill(CGRect(origin: .zero, size: size))
        }
    }

    /// Crea una imagen de prueba con texto
    static func createTestImageWithText(
        text: String,
        size: CGSize = CGSize(width: 800, height: 1200)
    ) -> UIImage {
        let renderer = UIGraphicsImageRenderer(size: size)
        return renderer.image { context in
            UIColor.white.setFill()
            context.fill(CGRect(origin: .zero, size: size))

            let paragraphStyle = NSMutableParagraphStyle()
            paragraphStyle.alignment = .center

            let attrs: [NSAttributedString.Key: Any] = [
                .font: UIFont.systemFont(ofSize: 48),
                .foregroundColor: UIColor.black,
                .paragraphStyle: paragraphStyle
            ]

            let string = NSAttributedString(string: text, attributes: attrs)
            let rect = CGRect(origin: .zero, size: size)
            string.draw(in: rect)
        }
    }

    /// Compara dos imágenes para verificar si son iguales
    static func compareImages(_ image1: UIImage?, _ image2: UIImage?) -> Bool {
        guard let img1 = image1, let img2 = image2 else {
            return image1 == nil && image2 == nil
        }

        return img1.pngData() == img2.pngData()
    }

    /// Verifica que una imagen no esté vacía
    static func isImageNotEmpty(_ image: UIImage?) -> Bool {
        guard let image = image else { return false }
        return image.size.width > 0 && image.size.height > 0
    }
}

// MARK: - File System Test Helpers

class FileSystemTestHelpers {

    /// Crea un directorio temporal para tests
    static func createTemporaryDirectory() throws -> URL {
        let tempDir = FileManager.default.temporaryDirectory
            .appendingPathComponent("test_\(UUID().uuidString)")

        try FileManager.default.createDirectory(
            at: tempDir,
            withIntermediateDirectories: true
        )

        return tempDir
    }

    /// Elimina un directorio temporal
    static func removeTemporaryDirectory(at url: URL) throws {
        try FileManager.default.removeItem(at: url)
    }

    /// Crea un archivo de prueba
    static func createTestFile(at url: URL, content: Data) throws {
        try content.write(to: url)
    }

    /// Verifica que un archivo existe
    static func fileExists(at url: URL) -> Bool {
        FileManager.default.fileExists(atPath: url.path)
    }

    /// Obtiene el tamaño de un archivo
    static func fileSize(at url: URL) -> Int64? {
        let attributes = try? FileManager.default.attributesOfItem(atPath: url.path)
        return attributes?[.size] as? Int64
    }

    /// Crea una estructura de directorios de prueba para capítulos
    static func createTestChapterStructure(
        mangaSlug: String,
        chapterNumber: Int,
        pageCount: Int,
        in directory: URL
    ) throws {
        let chapterDir = directory
            .appendingPathComponent(mangaSlug)
            .appendingPathComponent("Chapter\(chapterNumber)")

        try FileManager.default.createDirectory(
            at: chapterDir,
            withIntermediateDirectories: true
        )

        // Crear archivos de prueba
        for i in 0..<pageCount {
            let imageData = Data(repeating: UInt8(i), count: 1024)
            let fileURL = chapterDir.appendingPathComponent("page_\(i).jpg")
            try imageData.write(to: fileURL)
        }
    }
}

// MARK: - Storage Test Helpers

class StorageTestHelpers {

    /// Limpia todos los datos de almacenamiento
    static func clearAllStorage() {
        UserDefaults.standard.removeObject(forKey: "favoriteMangas")
        UserDefaults.standard.removeObject(forKey: "readingProgress")
        UserDefaults.standard.removeObject(forKey: "downloadedChaptersMetadata")

        let storageService = StorageService.shared
        storageService.clearAllDownloads()
    }

    /// Crea datos de prueba en el almacenamiento
    static func seedTestData(
        favoriteCount: Int = 5,
        progressCount: Int = 10,
        downloadedChapterCount: Int = 3
    ) {
        let storageService = StorageService.shared

        // Agregar favoritos
        for i in 0..<favoriteCount {
            storageService.saveFavorite(mangaSlug: "manga-\(i)")
        }

        // Agregar progreso
        for i in 0..<progressCount {
            let progress = TestDataFactory.createReadingProgress(
                mangaSlug: "manga-\(i % 3)",
                chapterNumber: i,
                pageNumber: i * 5
            )
            storageService.saveReadingProgress(progress)
        }

        // Agregar capítulos descargados
        for i in 0..<downloadedChapterCount {
            let chapter = TestDataFactory.createDownloadedChapter(
                mangaSlug: "manga-\(i % 2)",
                mangaTitle: "Manga \(i % 2)",
                chapterNumber: i,
                pages: TestDataFactory.createPages(count: 5)
            )
            storageService.saveDownloadedChapter(chapter)
        }
    }

    /// Verifica que el almacenamiento está vacío
    static func assertStorageIsEmpty() {
        let storageService = StorageService.shared

        XCTAssertTrue(
            storageService.getFavorites().isEmpty,
            "Favorites should be empty"
        )
        XCTAssertTrue(
            storageService.getAllReadingProgress().isEmpty,
            "Reading progress should be empty"
        )
        XCTAssertTrue(
            storageService.getDownloadedChapters().isEmpty,
            "Downloaded chapters should be empty"
        )
        XCTAssertEqual(
            storageService.getStorageSize(),
            0,
            "Storage size should be 0"
        )
    }
}

// MARK: - Async Test Helpers

class AsyncTestHelpers {

    /// Ejecuta una operación asíncrona con timeout
    static func executeWithTimeout<T>(
        timeout: TimeInterval = 5.0,
        operation: @escaping () async throws -> T
    ) async throws -> T {
        try await withThrowingTaskGroup(of: T.self) { group in
            group.addTask {
                return try await operation()
            }

            group.addTask {
                try await Task.sleep(nanoseconds: UInt64(timeout * 1_000_000_000))
                throw TimeoutError()
            }

            let result = try await group.next()
            group.cancelAll()

            guard let result = result else {
                throw TimeoutError()
            }

            return result
        }
    }

    struct TimeoutError: Error {
        let localizedDescription = "Operation timed out"
    }
}

// MARK: - Scraper Test Helpers

class ScraperTestHelpers {

    /// Simula una respuesta HTML para lista de capítulos
    static func mockChapterListHTML(mangaSlug: String) -> String {
        """
        <!DOCTYPE html>
        <html>
        <body>
            <a href="/leer/\(mangaSlug)/150">Chapter 150</a>
            <a href="/leer/\(mangaSlug)/149">Chapter 149</a>
            <a href="/leer/\(mangaSlug)/148">Chapter 148</a>
        </body>
        </html>
        """
    }

    /// Simula una respuesta HTML para imágenes de capítulo
    static func mockChapterImagesHTML() -> String {
        """
        <!DOCTYPE html>
        <html>
        <body>
            <img src="https://example.com/page1.jpg" alt="Page 1">
            <img src="https://example.com/page2.jpg" alt="Page 2">
            <img src="https://example.com/page3.jpg" alt="Page 3">
            <img src="https://example.com/avatar.jpg" alt="Avatar">
            <img src="https://example.com/logo.png" alt="Logo">
        </body>
        </html>
        """
    }

    /// Simula una respuesta HTML para información de manga
    static func mockMangaInfoHTML(title: String) -> String {
        """
        <!DOCTYPE html>
        <html>
        <head>
            <title>\(title) - ManhwaWeb</title>
        </head>
        <body>
            <h1>\(title)</h1>
            <p>This is a long description of the manga that contains more than 100 characters to meet the minimum requirement for extraction.</p>
            <a href="/genero/action">Action</a>
            <a href="/genero/fantasy">Fantasy</a>
            <div class="cover">
                <img src="https://example.com/cover.jpg" alt="Cover">
            </div>
            <p>Estado: PUBLICANDOSE</p>
        </body>
        </html>
        """
    }

    /// Simula un resultado de JavaScript para capítulos
    static func mockChapterJSResult() -> [[String: Any]] {
        return [
            [
                "number": 150,
                "title": "Chapter 150",
                "url": "https://manhwaweb.com/leer/test/150",
                "slug": "test/150"
            ],
            [
                "number": 149,
                "title": "Chapter 149",
                "url": "https://manhwaweb.com/leer/test/149",
                "slug": "test/149"
            ]
        ]
    }

    /// Simula un resultado de JavaScript para imágenes
    static func mockImagesJSResult() -> [String] {
        return [
            "https://example.com/page1.jpg",
            "https://example.com/page2.jpg",
            "https://example.com/page3.jpg"
        ]
    }

    /// Simula un resultado de JavaScript para info de manga
    static func mockMangaInfoJSResult() -> [String: Any] {
        return [
            "title": "Test Manga",
            "description": "A test manga description",
            "genres": ["Action", "Fantasy"],
            "status": "PUBLICANDOSE",
            "coverImage": "https://example.com/cover.jpg"
        ]
    }
}

// MARK: - Assertion Helpers

class AssertionHelpers {

    /// Afirma que dos arrays son iguales independientemente del orden
    static func assertArraysEqual<T: Equatable>(
        _ array1: [T],
        _ array2: [T],
        file: StaticString = #file,
        line: UInt = #line
    ) {
        XCTAssertEqual(
            array1.sorted(),
            array2.sorted(),
            "Arrays are not equal (order independent)",
            file: file,
            line: line
        )
    }

    /// Afirma que un array contiene elementos específicos
    static func assertArrayContains<T: Equatable>(
        _ array: [T],
        _ elements: [T],
        file: StaticString = #file,
        line: UInt = #line
    ) {
        for element in elements {
            XCTAssertTrue(
                array.contains(element),
                "Array does not contain expected element: \(element)",
                file: file,
                line: line
            )
        }
    }

    /// Afirma que una URL es válida
    static func assertValidURL(
        _ urlString: String,
        file: StaticString = #file,
        line: UInt = #line
    ) {
        XCTAssertNotNil(
            URL(string: urlString),
            "URL string is not valid: \(urlString)",
            file: file,
            line: line
        )
    }

    /// Afirma que un manga tiene datos válidos
    static func assertValidManga(
        _ manga: Manga,
        file: StaticString = #file,
        line: UInt = #line
    ) {
        XCTAssertFalse(
            manga.slug.isEmpty,
            "Manga slug should not be empty",
            file: file,
            line: line
        )
        XCTAssertFalse(
            manga.title.isEmpty,
            "Manga title should not be empty",
            file: file,
            line: line
        )
        XCTAssertFalse(
            manga.url.isEmpty,
            "Manga URL should not be empty",
            file: file,
            line: line
        )
        AssertionHelpers.assertValidURL(manga.url, file: file, line: line)
    }

    /// Afirma que un capítulo tiene datos válidos
    static func assertValidChapter(
        _ chapter: Chapter,
        file: StaticString = #file,
        line: UInt = #line
    ) {
        XCTAssertGreaterThan(
            chapter.number,
            0,
            "Chapter number should be greater than 0",
            file: file,
            line: line
        )
        XCTAssertFalse(
            chapter.title.isEmpty,
            "Chapter title should not be empty",
            file: file,
            line: line
        )
        XCTAssertFalse(
            chapter.url.isEmpty,
            "Chapter URL should not be empty",
            file: file,
            line: line
        )
    }
}

// MARK: - Performance Test Helpers

class PerformanceTestHelpers {

    /// Mide el tiempo de ejecución de una operación
    static func measureTime(_ operation: () -> Void) -> TimeInterval {
        let start = Date()
        operation()
        return Date().timeIntervalSince(start)
    }

    /// Mide el tiempo de ejecución de una operación asíncrona
    static func measureAsyncTime(_ operation: () async throws -> Void) async throws -> TimeInterval {
        let start = Date()
        try await operation()
        return Date().timeIntervalSince(start)
    }

    /// Ejecuta una operación múltiples veces y retorna el tiempo promedio
    static func averageTime(
        iterations: Int = 10,
        operation: () -> Void
    ) -> TimeInterval {
        var totalTime: TimeInterval = 0

        for _ in 0..<iterations {
            totalTime += measureTime(operation)
        }

        return totalTime / Double(iterations)
    }
}
