import XCTest
import WebKit
@testable import MangaReader

/// Tests para el ManhwaWebScraper
/// Mock de WKWebView y pruebas de parsing de HTML/JavaScript
@MainActor
final class ManhwaWebScraperTests: XCTestCase {

    var scraper: ManhwaWebScraper!

    // MARK: - Setup & Teardown

    override func setUp() async throws {
        try await super.setUp()
        scraper = ManhwaWebScraper.shared
    }

    override func tearDown() async throws {
        scraper = nil
        try await super.tearDown()
    }

    // MARK: - Scraper Error Tests

    func testScrapingErrorDescriptions() {
        // Test error descriptions
        XCTAssertEqual(
            ScrapingError.webViewNotInitialized.errorDescription,
            "WebView no está inicializado"
        )

        XCTAssertEqual(
            ScrapingError.pageLoadFailed.errorDescription,
            "Error al cargar la página"
        )

        XCTAssertEqual(
            ScrapingError.noContentFound.errorDescription,
            "No se encontró contenido"
        )

        XCTAssertEqual(
            ScrapingError.parsingError.errorDescription,
            "Error al procesar el contenido"
        )
    }

    func testScrapingErrorLocalizedError() {
        let error = ScrapingError.pageLoadFailed

        XCTAssertNotNil(error.errorDescription)
        let nsError = error as NSError
        XCTAssertNotNil(nsError.localizedDescription)
    }

    // MARK: - Mock WKWebView Tests

    func testWebViewInitialization() {
        // Test que el scraper se inicializa correctamente
        XCTAssertNotNil(scraper)
    }

    // MARK: - Chapter Parsing Tests (Simulated)

    func testChapterParsingFromJavaScriptResult() {
        // Simular el resultado de JavaScript
        let jsResult: [[String: Any]] = [
            [
                "number": 150,
                "title": "The Final Battle",
                "url": "https://manhwaweb.com/leer/solo-leveling/150",
                "slug": "solo-leveling/150"
            ],
            [
                "number": 149,
                "title": "The Beginning",
                "url": "https://manhwaweb.com/leer/solo-leveling/149",
                "slug": "solo-leveling/149"
            ],
            [
                "number": 148,
                "title": "Preparation",
                "url": "https://manhwaweb.com/leer/solo-leveling/148",
                "slug": "solo-leveling/148"
            ]
        ]

        // Parsear capítulos
        let chapters = jsResult.compactMap { dict -> Chapter? in
            guard let number = dict["number"] as? Int,
                  let title = dict["title"] as? String,
                  let url = dict["url"] as? String,
                  let slug = dict["slug"] as? String else {
                return nil
            }
            return Chapter(number: number, title: title, url: url, slug: slug)
        }

        // Verificar parsing
        XCTAssertEqual(chapters.count, 3)
        XCTAssertEqual(chapters[0].number, 150)
        XCTAssertEqual(chapters[0].title, "The Final Battle")
        XCTAssertEqual(chapters[1].number, 149)
        XCTAssertEqual(chapters[2].number, 148)
    }

    func testChapterParsingWithInvalidData() {
        // Simular resultado con datos inválidos
        let jsResult: [[String: Any]] = [
            [
                "number": "not-a-number",
                "title": "Invalid Chapter",
                "url": "https://manhwaweb.com/leer/test/1",
                "slug": "test/1"
            ],
            [
                "number": 10,
                "title": "",
                "url": "https://manhwaweb.com/leer/test/10",
                "slug": "test/10"
            ],
            [
                "number": 5,
                "title": "Valid",
                "url": "invalid-url",
                "slug": "test/5"
            ]
        ]

        // Parsear - debería fallar para todos los casos inválidos
        let chapters = jsResult.compactMap { dict -> Chapter? in
            guard let number = dict["number"] as? Int,
                  let title = dict["title"] as? String,
                  let url = dict["url"] as? String,
                  let slug = dict["slug"] as? String else {
                return nil
            }
            return !title.isEmpty && !url.isEmpty ? Chapter(number: number, title: title, url: url, slug: slug) : nil
        }

        // Todos deberían ser nil por datos inválidos
        XCTAssertTrue(chapters.isEmpty)
    }

    func testChapterDeduplication() {
        // Simular duplicados en el resultado
        let jsResult: [[String: Any]] = [
            ["number": 10, "title": "Chapter 10", "url": "url1", "slug": "slug1"],
            ["number": 10, "title": "Chapter 10 Duplicate", "url": "url2", "slug": "slug2"],
            ["number": 9, "title": "Chapter 9", "url": "url3", "slug": "slug3"],
            ["number": 9, "title": "Chapter 9 Duplicate", "url": "url4", "slug": "slug4"]
        ]

        // Aplicar lógica de deduplicación
        let unique = jsResult.filter { (chapter) in
            jsResult.firstIndex(where: { ($0["number"] as? Int) == (chapter["number"] as? Int) }) ==
            jsResult.firstIndex(of: chapter)
        }

        XCTAssertEqual(unique.count, 2, "Should have only 2 unique chapters")
    }

    func testChapterSorting() {
        // Simular capítulos desordenados
        let chapters = [
            Chapter(number: 5, title: "Ch 5", url: "", slug: ""),
            Chapter(number: 10, title: "Ch 10", url: "", slug: ""),
            Chapter(number: 1, title: "Ch 1", url: "", slug: ""),
            Chapter(number: 7, title: "Ch 7", url: "", slug: "")
        ]

        // Ordenar descendente (como hace el scraper)
        let sorted = chapters.sorted { $0.number > $1.number }

        XCTAssertEqual(sorted[0].number, 10)
        XCTAssertEqual(sorted[1].number, 7)
        XCTAssertEqual(sorted[2].number, 5)
        XCTAssertEqual(sorted[3].number, 1)
    }

    // MARK: - Image Parsing Tests (Simulated)

    func testImageParsingFromJavaScriptResult() {
        // Simular el resultado de JavaScript para imágenes
        let jsResult: [String] = [
            "https://example.com/image1.jpg",
            "https://example.com/image2.jpg",
            "https://example.com/image3.jpg",
            "https://example.com/image1.jpg", // Duplicado
            "https://example.com/avatar.jpg",
            "https://example.com/logo.png",
            "https://example.com/image4.jpg"
        ]

        // Aplicar filtros del scraper
        let filteredImages = jsResult.filter { url in
            let lowercased = url.lowercased()

            // Filtrar UI elements
            let isUIElement =
                lowercased.contains("avatar") ||
                lowercased.contains("icon") ||
                lowercased.contains("logo") ||
                lowercased.contains("button")

            return !isUIElement && lowercased.contains("http")
        }

        // Eliminar duplicados
        let uniqueImages = Array(Set(filteredImages))

        XCTAssertEqual(uniqueImages.count, 4, "Should have 4 unique non-UI images")
        XCTAssertTrue(uniqueImages.contains("https://example.com/image1.jpg"))
        XCTAssertFalse(uniqueImages.contains("https://example.com/avatar.jpg"))
        XCTAssertFalse(uniqueImages.contains("https://example.com/logo.png"))
    }

    func testImageParsingWithEmptyArray() {
        let jsResult: [String] = []

        // Aplicar parsing
        let images = jsResult.filter { $0.contains("http") }

        XCTAssertTrue(images.isEmpty)
    }

    func testImageParsingWithInvalidURLs() {
        let jsResult: [String] = [
            "not-a-url",
            "",
            "ftp://invalid-protocol.com/image.jpg",
            "https://valid.com/image.jpg"
        ]

        let validImages = jsResult.filter { $0.hasPrefix("https://") }

        XCTAssertEqual(validImages.count, 1)
        XCTAssertEqual(validImages.first, "https://valid.com/image.jpg")
    }

    // MARK: - Manga Info Parsing Tests (Simulated)

    func testMangaInfoParsingFromJavaScriptResult() {
        // Simular el resultado de JavaScript
        let jsResult: [String: Any] = [
            "title": "Solo Leveling",
            "description": "The weakest hunter becomes the strongest in a world where dungeons have appeared.",
            "genres": ["Action", "Adventure", "Fantasy"],
            "status": "PUBLICANDOSE",
            "coverImage": "https://example.com/cover.jpg"
        ]

        // Parsear información del manga
        let title = jsResult["title"] as? String ?? "Unknown"
        let description = jsResult["description"] as? String ?? ""
        let genres = jsResult["genres"] as? [String] ?? []
        let status = jsResult["status"] as? String ?? "UNKNOWN"
        let coverImage = jsResult["coverImage"] as? String

        let manga = Manga(
            slug: "solo-leveling",
            title: title,
            description: description,
            genres: genres,
            status: status,
            url: "https://manhwaweb.com/manga/solo-leveling",
            coverImage: coverImage?.isEmpty == false ? coverImage : nil
        )

        XCTAssertEqual(manga.title, "Solo Leveling")
        XCTAssertEqual(manga.genres.count, 3)
        XCTAssertTrue(manga.genres.contains("Action"))
        XCTAssertEqual(manga.status, "PUBLICANDOSE")
        XCTAssertEqual(manga.coverImage, "https://example.com/cover.jpg")
    }

    func testMangaInfoParsingWithEmptyFields() {
        let jsResult: [String: Any] = [
            "title": "",
            "description": "",
            "genres": [],
            "status": "",
            "coverImage": ""
        ]

        let title = jsResult["title"] as? String ?? "Unknown"
        let description = jsResult["description"] as? String ?? ""
        let genres = jsResult["genres"] as? [String] ?? []
        let status = jsResult["status"] as? String ?? "UNKNOWN"
        let coverImage = jsResult["coverImage"] as? String

        let manga = Manga(
            slug: "test",
            title: title,
            description: description,
            genres: genres,
            status: status,
            url: "url",
            coverImage: coverImage?.isEmpty == false ? coverImage : nil
        )

        XCTAssertEqual(manga.title, "Unknown")
        XCTAssertTrue(manga.description.isEmpty)
        XCTAssertTrue(manga.genres.isEmpty)
        XCTAssertEqual(manga.status, "")
        XCTAssertNil(manga.coverImage)
    }

    func testMangaStatusParsing() {
        // Simular diferentes estados
        let testCases: [(String, String)] = [
            ("PUBLICANDOSE", "PUBLICANDOSE"),
            ("FINALIZADO", "FINALIZADO"),
            ("EN PAUSA", "EN_PAUSA"),
            ("EN_ESPERA", "EN_ESPERA"),
            ("Unknown", "UNKNOWN")
        ]

        for (input, expected) in testCases {
            let normalized = input.uppercased().replacingOccurrences(of: " ", with: "_")
            XCTAssertEqual(normalized, expected)
        }
    }

    // MARK: - URL Construction Tests

    func testMangaURLConstruction() {
        let slug = "solo-leveling"
        let expectedURL = "https://manhwaweb.com/manga/\(slug)"

        // Simular construcción de URL
        let url = URL(string: expectedURL)

        XCTAssertNotNil(url)
        XCTAssertEqual(url?.absoluteString, expectedURL)
    }

    func testChapterURLConstruction() {
        let slug = "solo-leveling/150"
        let expectedURL = "https://manhwaweb.com/leer/\(slug)"

        let url = URL(string: expectedURL)

        XCTAssertNotNil(url)
        XCTAssertEqual(url?.absoluteString, expectedURL)
    }

    func testURLConstructionWithSpecialCharacters() {
        let slug = "manga-with-special-chars"
        let expectedURL = "https://manhwaweb.com/manga/\(slug)"

        let url = URL(string: expectedURL)

        XCTAssertNotNil(url)
        XCTAssertEqual(url?.absoluteString, expectedURL)
    }

    // MARK: - Edge Cases Tests

    func testChapterNumberExtraction() {
        // Simular regex extraction de números de capítulo
        let testCases: [(String, Int?)] = [
            ("https://manhwaweb.com/leer/solo-leveling/150", 150),
            ("https://manhwaweb.com/leer/solo-leveling/150/", 150),
            ("https://manhwaweb.com/leer/solo-leveling/50?page=2", 50),
            ("https://manhwaweb.com/leer/test", nil),
            ("", nil)
        ]

        let pattern = "(\\d+)(?:\\/|\\?|\\s*$)"

        for (url, expectedNumber) in testCases {
            let regex = try? NSRegularExpression(pattern: pattern)
            let range = NSRange(url.startIndex..., in: url)
            let match = regex?.firstMatch(in: url, range: range)

            if let match = match, let matchRange = Range(match.range(at: 1), in: url) {
                let number = Int(String(url[matchRange]))
                XCTAssertEqual(number, expectedNumber)
            } else {
                XCTAssertNil(expectedNumber, "URL \(url) should not extract a number")
            }
        }
    }

    func testChapterSlugExtraction() {
        let href = "/leer/solo-leveling/150"

        // Simular extracción de slug
        let slug = href.replacingOccurrences(of: "/leer/", with: "").replacingOccurrences(of: "^/", with: "", options: .regularExpression)

        XCTAssertEqual(slug, "solo-leveling/150")
    }

    func testDuplicateRemovalPreservingOrder() {
        let chapters = [
            ["number": 10, "title": "Ch 10"],
            ["number": 9, "title": "Ch 9"],
            ["number": 10, "title": "Ch 10 Dup"],
            ["number": 8, "title": "Ch 8"],
            ["number": 9, "title": "Ch 9 Dup"]
        ]

        // Eliminar duplicados preservando el orden de primera aparición
        let seen = NSMutableSet()
        let unique = chapters.filter { chapter in
            let number = chapter["number"] as! Int
            if seen.contains(number) {
                return false
            }
            seen.add(number)
            return true
        }

        XCTAssertEqual(unique.count, 3)
        XCTAssertEqual((unique[0]["number"] as? Int), 10)
        XCTAssertEqual((unique[1]["number"] as? Int), 9)
        XCTAssertEqual((unique[2]["number"] as? Int), 8)
    }

    // MARK: - Async Behavior Tests

    func testScraperIsMainActor() {
        // Verificar que el scraper opera en MainActor
        XCTAssertTrue(MainActor.isMainActor, "Scraper should run on main actor")
    }

    // MARK: - Performance Tests

    func testChapterParsingPerformance() {
        let jsResult: [[String: Any]] = (0..<1000).map { i in
            [
                "number": i,
                "title": "Chapter \(i)",
                "url": "https://manhwaweb.com/leer/test/\(i)",
                "slug": "test/\(i)"
            ]
        }

        measure {
            _ = jsResult.compactMap { dict -> Chapter? in
                guard let number = dict["number"] as? Int,
                      let title = dict["title"] as? String,
                      let url = dict["url"] as? String,
                      let slug = dict["slug"] as? String else {
                    return nil
                }
                return Chapter(number: number, title: title, url: url, slug: slug)
            }
        }
    }

    func testImageFilteringPerformance() {
        // Crear 10,000 URLs con algunos duplicados
        var urls: [String] = []
        for i in 0..<10000 {
            urls.append("https://example.com/image\(i).jpg")
            if i % 100 == 0 {
                urls.append("https://example.com/avatar\(i).jpg")
                urls.append("https://example.com/icon\(i).png")
            }
        }

        measure {
            let filtered = urls.filter { url in
                let lowercased = url.lowercased()
                return !lowercased.contains("avatar") &&
                       !lowercased.contains("icon") &&
                       !lowercased.contains("logo")
            }
            _ = Array(Set(filtered))
        }
    }

    func testChapterSortingPerformance() {
        var chapters: [Chapter] = []
        for i in 0..<1000 {
            chapters.append(Chapter(number: Int.random(in: 1...1000), title: "Ch", url: "", slug: ""))
        }

        measure {
            _ = chapters.sorted { $0.number > $1.number }
        }
    }

    // MARK: - Integration Simulation Tests

    func testCompleteScrapingFlowSimulation() {
        // Simular el flujo completo de scraping

        // 1. Simular respuesta de capítulos
        let chapterJSResponse: [[String: Any]] = [
            ["number": 10, "title": "Chapter 10", "url": "url10", "slug": "slug10"],
            ["number": 9, "title": "Chapter 9", "url": "url9", "slug": "slug9"]
        ]

        let chapters = chapterJSResponse.compactMap { dict -> Chapter? in
            guard let number = dict["number"] as? Int,
                  let title = dict["title"] as? String,
                  let url = dict["url"] as? String,
                  let slug = dict["slug"] as? String else {
                return nil
            }
            return Chapter(number: number, title: title, url: url, slug: slug)
        }

        XCTAssertEqual(chapters.count, 2)

        // 2. Simular respuesta de imágenes para el primer capítulo
        let imagesJSResponse: [String] = [
            "https://example.com/page1.jpg",
            "https://example.com/page2.jpg",
            "https://example.com/page3.jpg"
        ]

        let images = imagesJSResponse.filter { $0.hasPrefix("https://") }
        XCTAssertEqual(images.count, 3)

        // 3. Crear páginas
        let pages = images.enumerated().map { index, url in
            MangaPage(url: url, index: index)
        }

        XCTAssertEqual(pages.count, 3)
        XCTAssertEqual(pages[0].url, "https://example.com/page1.jpg")
        XCTAssertEqual(pages[0].index, 0)
    }
}
