import Foundation

enum M3UParserError: LocalizedError {
    case invalidFormat
    case noItemsFound
    case parsingFailed(String)

    var errorDescription: String? {
        switch self {
        case .invalidFormat:
            return "The M3U playlist format is invalid"
        case .noItemsFound:
            return "No VOD items were found in the playlist"
        case .parsingFailed(let details):
            return "Parsing failed: \(details)"
        }
    }
}

class M3UParser {
    /// Parses M3U data using parallel processing for improved performance
    func parse(data: String) throws -> [VODItem] {
        let startTime = Date()

        // Remove UTF-8 BOM if present
        let cleanedData = data.hasPrefix("\u{FEFF}") ? String(data.dropFirst()) : data

        // Split by newlines (handles \n, \r, and \r\n)
        let lines = cleanedData.components(separatedBy: .newlines)

        // Check for valid M3U header
        let firstLine = lines.first?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
        guard firstLine == "#EXTM3U" || firstLine.hasPrefix("#EXTM3U ") else {
            print("M3UParser: Invalid format - first line is: '\(firstLine.prefix(50))'")
            throw M3UParserError.invalidFormat
        }

        // Phase 1: Collect all EXTINF/URL pairs (single-threaded, fast)
        var pairs: [(infoLine: String, urlLine: String)] = []
        pairs.reserveCapacity(150000)

        var index = 1
        while index < lines.count {
            let line = lines[index].trimmingCharacters(in: .whitespacesAndNewlines)

            if line.hasPrefix("#EXTINF:") {
                // Find next non-empty line for URL
                var urlIndex = index + 1
                while urlIndex < lines.count {
                    let urlLine = lines[urlIndex].trimmingCharacters(in: .whitespacesAndNewlines)
                    if !urlLine.isEmpty {
                        if !urlLine.hasPrefix("#") {
                            pairs.append((infoLine: line, urlLine: urlLine))
                            index = urlIndex + 1
                        } else {
                            index = urlIndex
                        }
                        break
                    }
                    urlIndex += 1
                }
                if urlIndex >= lines.count {
                    index = urlIndex
                }
            } else {
                index += 1
            }
        }

        let pairCollectionTime = Date().timeIntervalSince(startTime)
        print("M3UParser: Collected \(pairs.count) pairs in \(String(format: "%.2f", pairCollectionTime))s")

        // Phase 2: Parse pairs in parallel using chunks
        let chunkSize = 5000
        let chunks = stride(from: 0, to: pairs.count, by: chunkSize).map { startIndex in
            Array(pairs[startIndex..<min(startIndex + chunkSize, pairs.count)])
        }

        var allItems: [[VODItem]] = Array(repeating: [], count: chunks.count)

        // Process chunks in parallel using DispatchQueue
        DispatchQueue.concurrentPerform(iterations: chunks.count) { chunkIndex in
            let chunk = chunks[chunkIndex]
            var chunkItems: [VODItem] = []
            chunkItems.reserveCapacity(chunk.count)

            for pair in chunk {
                if let item = parseVODItem(infoLine: pair.infoLine, urlLine: pair.urlLine) {
                    chunkItems.append(item)
                }
            }

            allItems[chunkIndex] = chunkItems
        }

        // Flatten results
        let vodItems = allItems.flatMap { $0 }

        let totalTime = Date().timeIntervalSince(startTime)
        print("M3UParser: Parsed \(vodItems.count) VOD items from \(lines.count) lines in \(String(format: "%.2f", totalTime))s")

        if vodItems.isEmpty {
            throw M3UParserError.noItemsFound
        }

        return vodItems
    }

    private func parseVODItem(infoLine: String, urlLine: String) -> VODItem? {
        let tvgID = infoLine.extractAttribute("tvg-id")
        let tvgName = infoLine.extractAttribute("tvg-name")
        let tvgLogo = infoLine.extractAttribute("tvg-logo")
        let groupTitle = infoLine.extractAttribute("group-title")

        let displayName = infoLine.extractAfterComma() ?? tvgName ?? "Unknown"

        // Use tvg-id only if it is not empty, otherwise generate unique UUID
        let id: String
        if let tvgID = tvgID, !tvgID.isEmpty {
            id = tvgID
        } else {
            id = UUID().uuidString
        }

        return VODItem(
            id: id,
            name: displayName,
            streamURL: urlLine,
            iconURL: tvgLogo,
            categoryID: groupTitle,
            description: nil,
            duration: nil,
            rating: nil,
            added: nil
        )
    }

    func extractCategories(from items: [VODItem]) -> [Category] {
        var categoryDict: [String: String] = [:]

        for item in items {
            if let categoryID = item.categoryID {
                categoryDict[categoryID] = categoryID
            }
        }

        return categoryDict.map { Category(id: $0.key, name: $0.value) }
            .sorted { $0.name < $1.name }
    }
}
