import Foundation
import CryptoKit

struct CSVSnapshot {
    let latestVideo: VideoRecord?
    let contentHash: String
    let diagnosticMessage: String?
}

struct CSVVideoStore {
    static let fileName = "bilibili_latest_videos_259655230.csv"
    static let baseServerURL = "https://huaiyumacbook-pro.tail432a28.ts.net"
    static let defaultCSVURL = "\(baseServerURL)/\(fileName)"
    static let tokenRegistrationURL = "\(baseServerURL)/register-token"
    static let legacySimulatorCSVURL = "http://127.0.0.1:8000/\(fileName)"
    static let legacyLocalCSVURL = "http://192.168.71.102:8000/\(fileName)"

    enum StoreError: LocalizedError {
        case invalidURL(String)
        case invalidResponse
        case requestFailed(Int)
        case unreadableText
        case unparseableCSV(String)

        var errorDescription: String? {
            switch self {
            case .invalidURL(let text):
                "服务器地址无效：\(text)"
            case .invalidResponse:
                "服务器响应无效"
            case .requestFailed(let statusCode):
                "服务器请求失败：HTTP \(statusCode)"
            case .unreadableText:
                "CSV 文本编码无法读取"
            case .unparseableCSV(let details):
                "CSV 已下载但无法解析视频记录。\(details)"
            }
        }
    }

    private let session: URLSession

    init(session: URLSession = .shared) {
        self.session = session
    }

    func loadSnapshot(from csvURLText: String) async throws -> CSVSnapshot {
        guard let csvURL = URL(string: csvURLText.trimmingCharacters(in: .whitespacesAndNewlines)) else {
            throw StoreError.invalidURL(csvURLText)
        }

        var request = URLRequest(url: csvURL)
        request.cachePolicy = .reloadIgnoringLocalCacheData
        request.timeoutInterval = 20
        request.setValue("text/csv,*/*;q=0.8", forHTTPHeaderField: "Accept")
        request.setValue("no-cache", forHTTPHeaderField: "Cache-Control")

        let (data, response) = try await session.data(for: request)
        guard let httpResponse = response as? HTTPURLResponse else {
            throw StoreError.invalidResponse
        }

        guard (200..<300).contains(httpResponse.statusCode) else {
            throw StoreError.requestFailed(httpResponse.statusCode)
        }

        guard let text = String(data: data, encoding: .utf8) else {
            throw StoreError.unreadableText
        }

        let contentHash = Self.contentHash(for: data)
        if text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
            return CSVSnapshot(
                latestVideo: nil,
                contentHash: contentHash,
                diagnosticMessage: "CSV 文件为空"
            )
        }

        let latestVideo = Self.latestVideo(fromCSVText: text) ?? Self.fallbackVideo(fromCSVText: text)
        return CSVSnapshot(
            latestVideo: latestVideo,
            contentHash: contentHash,
            diagnosticMessage: latestVideo == nil ? Self.diagnosticSummary(forCSVText: text) : nil
        )
    }

    func loadLatestVideo(from csvURLText: String) async throws -> VideoRecord? {
        let snapshot = try await loadSnapshot(from: csvURLText)
        if snapshot.latestVideo == nil, let diagnosticMessage = snapshot.diagnosticMessage {
            throw StoreError.unparseableCSV(diagnosticMessage)
        }
        return snapshot.latestVideo
    }

    static func latestVideo(fromCSVText text: String) -> VideoRecord? {
        let rows = CSVParser.parse(text)
        guard let header = rows.first else {
            return nil
        }

        let normalizedHeader = header.map { canonicalHeader($0) }
        let records = rows.dropFirst().compactMap { row -> VideoRecord? in
            let dictionary = Dictionary(uniqueKeysWithValues: zip(normalizedHeader, row))
            return VideoRecord.from(row: dictionary)
        }

        return records.max { lhs, rhs in
            lhs.publishDate < rhs.publishDate
        }
    }

    private static func fallbackVideo(fromCSVText text: String) -> VideoRecord? {
        let lines = text
            .replacingOccurrences(of: "\u{feff}", with: "")
            .split(whereSeparator: \.isNewline)
            .map { String($0).trimmingCharacters(in: .whitespacesAndNewlines) }
            .filter { !$0.isEmpty }

        guard lines.count >= 2 else {
            return nil
        }

        let values = lines[1].split(separator: ",", maxSplits: 2).map(String.init)
        guard values.count == 3 else {
            return nil
        }

        return VideoRecord(
            title: values[0].trimmingCharacters(in: .whitespacesAndNewlines),
            publishTimeText: values[1].trimmingCharacters(in: .whitespacesAndNewlines),
            videoURLText: values[2].trimmingCharacters(in: .whitespacesAndNewlines)
        )
    }

    private static func canonicalHeader(_ header: String) -> String {
        header
            .replacingOccurrences(of: "\u{feff}", with: "")
            .trimmingCharacters(in: .whitespacesAndNewlines)
            .lowercased()
    }

    private static func contentHash(for data: Data) -> String {
        SHA256.hash(data: data)
            .map { String(format: "%02x", $0) }
            .joined()
    }

    private static func diagnosticSummary(forCSVText text: String) -> String {
        let rows = CSVParser.parse(text)
        let headerText = rows.first?.joined(separator: ", ") ?? "未找到表头"
        let rowText = rows.dropFirst().first?.joined(separator: ", ") ?? "未找到数据行"
        let fieldDiagnostic: String
        if let firstRow = rows.dropFirst().first {
            let normalizedHeader = rows.first?.map { canonicalHeader($0) } ?? []
            let dictionary = Dictionary(uniqueKeysWithValues: zip(normalizedHeader, firstRow))
            fieldDiagnostic = VideoRecord.diagnosticMessage(for: dictionary)
        } else {
            fieldDiagnostic = "没有可检查的数据行"
        }
        let preview = text
            .replacingOccurrences(of: "\u{feff}", with: "")
            .split(separator: "\n", omittingEmptySubsequences: false)
            .prefix(3)
            .joined(separator: "\n")

        return "表头：\(headerText)。首行：\(rowText)。字段检查：\(fieldDiagnostic)。内容预览：\(preview)"
    }
}

enum CSVParser {
    static func parse(_ text: String) -> [[String]] {
        let normalizedText = text.replacingOccurrences(of: "\u{feff}", with: "")
        let characters = Array(normalizedText)
        var rows: [[String]] = []
        var row: [String] = []
        var field = ""
        var isInsideQuotes = false
        var index = 0

        while index < characters.count {
            let character = characters[index]

            if character == "\"" {
                let nextIndex = index + 1
                if isInsideQuotes, nextIndex < characters.count, characters[nextIndex] == "\"" {
                    field.append("\"")
                    index += 1
                } else {
                    isInsideQuotes.toggle()
                }
            } else if character == ",", !isInsideQuotes {
                row.append(field)
                field = ""
            } else if (character == "\n" || character == "\r"), !isInsideQuotes {
                row.append(field)
                appendRow(&rows, row)
                row = []
                field = ""

                let nextIndex = index + 1
                if character == "\r", nextIndex < characters.count, characters[nextIndex] == "\n" {
                    index += 1
                }
            } else {
                field.append(character)
            }

            index += 1
        }

        if !field.isEmpty || !row.isEmpty {
            row.append(field)
            appendRow(&rows, row)
        }

        return rows
    }

    private static func appendRow(_ rows: inout [[String]], _ row: [String]) {
        let trimmedRow = row.map {
            $0.trimmingCharacters(in: .whitespacesAndNewlines)
        }
        if trimmedRow.contains(where: { !$0.isEmpty }) {
            rows.append(trimmedRow)
        }
    }
}
