import os
import UIKit
import WebKit

/// 统一下载管理器：URLSession + Cookie 同步 + 分享 sheet。
///
/// **策略**（方案 C：统一 URLSession + Cookie 同步）：
/// 1. 从 WKWebsiteDataStore.httpCookieStore 获取所有 Cookie（含 HttpOnly）
/// 2. 注入 Cookie 到 URLRequest
/// 3. URLSession.shared.downloadTask 下载文件到 tmp
/// 4. 弹 UIActivityViewController 分享 sheet
/// 5. 失败时自动 fallback 到 UIApplication.shared.open(url)（Safari）
///
/// **优势**（相比旧 WKDownloadDelegate 方案）：
/// - 消除 WebKit `didBecomeDownload` 首次不触发的已知问题（design.md D22）
/// - 全版本统一：iOS 14.3+ 全部走同一路径，无需 1 秒 fallback 定时器
/// - 客户通用性：Cookie 同步确保客户自有业务的鉴权下载也能正常工作
/// - 代码简洁：单一路径，无 WKDownload + fallback 双路径嵌套
///
/// **Android 等价**：`DownloadListenerInstaller`（DownloadManager + Intent 兜底）。
final class QianDownloadManager: NSObject {

    private let onEvent: ((SdkEvent) -> Void)?
    private weak var hostController: UIViewController?

    /// 当前下载的原始 URL（用于事件上报和 fallback）
    private var originalURL: URL?

    init(onEvent: ((SdkEvent) -> Void)?, hostController: UIViewController?) {
        self.onEvent = onEvent
        self.hostController = hostController
    }

    /// 更新 hostController（容器模式下 VC 可能延迟设置）
    func updateHostController(_ vc: UIViewController?) {
        self.hostController = vc
    }

    // MARK: - 统一下载入口

    /// 执行下载：根据配置决定是否同步 Cookie → URLSession 下载 → 分享 sheet。
    ///
    /// - Parameters:
    ///   - url: 下载文件的 URL
    ///   - response: 可选的 URLResponse（用于提取 MIME / Content-Disposition）
    ///   - dataStore: WKWebsiteDataStore，用于获取 Cookie（默认 `.default()`）
    ///   - downloadWithCookies: 是否从 WKWebView 同步 Cookie 到下载请求（默认 `false`）
    func download(url: URL, response: URLResponse?, dataStore: WKWebsiteDataStore = .default(), downloadWithCookies: Bool = false) {
        originalURL = url

        let mime = response?.mimeType ?? ""
        let disposition = HTTPHeaderCompat.value(for: "Content-Disposition", in: response) ?? ""
        onEvent?(.downloadStart(url: url.absoluteString, mimeType: mime, contentDisposition: disposition))

        QianLog.download.info("Starting download: \(url.absoluteString), withCookies=\(downloadWithCookies)")

        guard downloadWithCookies else {
            let request = URLRequest(url: url)
            self.performDownload(request: request)
            return
        }

        // 从 WKWebsiteDataStore 获取所有 Cookie（含 HttpOnly），注入到 URLRequest
        dataStore.httpCookieStore.getAllCookies { [weak self] cookies in
            guard let self = self else { return }

            var request = URLRequest(url: url)
            // 注入 Cookie 到请求头
            let cookieHeaders = HTTPCookie.requestHeaderFields(with: cookies.filter { cookie in
                // 只注入与目标 URL 域名匹配的 Cookie
                guard let host = url.host else { return false }
                let domain = cookie.domain.hasPrefix(".") ? String(cookie.domain.dropFirst()) : cookie.domain
                return host == domain || host.hasSuffix(".\(domain)")
            })
            for (key, value) in cookieHeaders {
                request.setValue(value, forHTTPHeaderField: key)
            }

            QianLog.download.debug("Injected \(cookies.count) cookies, matched headers: \(cookieHeaders.count)")

            self.performDownload(request: request)
        }
    }

    // MARK: - URLSession 下载

    private func performDownload(request: URLRequest) {
        let url = request.url!

        let task = URLSession.shared.downloadTask(with: request) { [weak self] tmpURL, urlResponse, error in
            guard let self = self else { return }

            if let error = error {
                QianLog.download.error("Download failed: \(error.localizedDescription)")
                self.onEvent?(.downloadFailed(url: url.absoluteString, reason: error.localizedDescription))
                DispatchQueue.main.async {
                    self.fallbackToSafari(url: url)
                }
                return
            }

            guard let tmpURL = tmpURL else {
                QianLog.download.error("Download: tmpURL is nil")
                self.onEvent?(.downloadFailed(url: url.absoluteString, reason: "Downloaded file URL is nil"))
                return
            }

            // 确定文件名（sanitize 防路径穿越：服务器可返回含 ../ 或 / 的恶意名）
            let safeName = Self.sanitizedFilename(urlResponse?.suggestedFilename ?? url.lastPathComponent)
            let destDir = FileManager.default.temporaryDirectory
                .appendingPathComponent("QianDownloads", isDirectory: true)
            try? FileManager.default.createDirectory(at: destDir, withIntermediateDirectories: true)
            // 落盘二次防御：safeName 必须是"纯文件名"（不含任何路径分隔符，也不是 . / ..）。
            // sanitizedFilename 已剥离 / 与 ../，此处按文件名层面做等值校验即可，
            // 不再用 standardizedFileURL 绝对路径 hasPrefix 比较——后者在真机上因
            // /var → /private/var 符号链接对"已存在的 destDir"与"尚不存在的 destURL"
            // 解析不对称，会把合法文件名（如 sample-2-pages.pdf）误判为路径穿越并拒绝下载。
            guard safeName == (safeName as NSString).lastPathComponent,
                  !safeName.contains("/"),
                  safeName != ".", safeName != ".." else {
                QianLog.download.error("Download: illegal filename, rejected")
                self.onEvent?(.downloadFailed(url: url.absoluteString, reason: "Illegal filename"))
                return
            }
            let destURL = destDir.appendingPathComponent(safeName)
            try? FileManager.default.removeItem(at: destURL)

            do {
                try FileManager.default.moveItem(at: tmpURL, to: destURL)
                QianLog.download.info("Download success: \(destURL.path)")
                self.onEvent?(.downloadCompleted(url: url.absoluteString, localPath: destURL.path))
                DispatchQueue.main.async { [weak self] in
                    self?.presentShareSheetWithRetry(fileURL: destURL, retryCount: 5)
                }
            } catch {
                QianLog.download.error("Download: move file failed: \(error.localizedDescription)")
                self.onEvent?(.downloadFailed(url: url.absoluteString, reason: error.localizedDescription))
            }
        }
        task.resume()
    }

    // MARK: - 分享 Sheet（带重试）

    /// 带重试的分享 sheet 弹出。
    /// 当 `location.href` 触发主框架下载时,WKWebView 内部状态转换可能导致
    /// VC 短暂不可 present,通过延迟重试解决。
    private func presentShareSheetWithRetry(fileURL: URL, retryCount: Int) {
        guard let vc = hostController else {
            QianLog.download.warning("presentShareSheetWithRetry: hostController is nil, fallback")
            fallbackToSafari(url: originalURL)
            cleanupFile(fileURL)
            return
        }

        if canPresent(on: vc) {
            QianLog.download.debug("presentShareSheetWithRetry: canPresent=true, presenting (remaining retries: \(retryCount))")
            presentShareSheet(fileURL: fileURL)
        } else if retryCount > 0 {
            QianLog.download.debug("presentShareSheetWithRetry: canPresent=false, retrying in 0.3s (remaining: \(retryCount))")
            QianLog.download.debug("  isBeingDismissed=\(vc.isBeingDismissed), isBeingPresented=\(vc.isBeingPresented), window=\(String(describing: vc.view.window))")
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { [weak self] in
                self?.presentShareSheetWithRetry(fileURL: fileURL, retryCount: retryCount - 1)
            }
        } else {
            QianLog.download.warning("presentShareSheetWithRetry: retries exhausted, fallback to Safari")
            fallbackToSafari(url: originalURL)
            cleanupFile(fileURL)
        }
    }

    private func presentShareSheet(fileURL: URL) {
        guard let vc = hostController else { return }

        let activityVC = UIActivityViewController(
            activityItems: [fileURL],
            applicationActivities: nil
        )
        // iPad 需要 popoverPresentationController
        if let popover = activityVC.popoverPresentationController {
            popover.sourceView = vc.view
            popover.sourceRect = CGRect(x: vc.view.bounds.midX, y: vc.view.bounds.midY, width: 0, height: 0)
            popover.permittedArrowDirections = []
        }

        activityVC.completionWithItemsHandler = { [weak self] _, _, _, _ in
            // 分享完成后清理 tmp 文件
            self?.cleanupFile(fileURL)
        }

        vc.present(activityVC, animated: true)
    }

    // MARK: - Fallback

    private func fallbackToSafari(url: URL?) {
        guard let url = url else { return }
        UIApplication.shared.open(url, options: [:]) { [weak self] ok in
            if !ok {
                self?.onEvent?(.downloadFailed(
                    url: url.absoluteString,
                    reason: "Safari fallback: UIApplication.open returned false"
                ))
            }
        }
    }

    // MARK: - Helpers

    private func canPresent(on vc: UIViewController) -> Bool {
        !vc.isBeingDismissed && !vc.isBeingPresented && vc.view.window != nil
    }

    /// 归一化下载文件名，防止路径穿越。
    ///
    /// 规则：
    /// 1. 只取最后一段路径（`lastPathComponent` 剔除 `../` 等路径成分）
    /// 2. 替换分隔符 / HFS 冒号 / 空字符为 `_`
    /// 3. 纯 `.` / `..` / 空名 → 时间戳兜底名
    /// 4. 超长（>200）截断，防超长名写入失败
    static func sanitizedFilename(_ raw: String) -> String {
        var name = (raw as NSString).lastPathComponent
        let illegal = CharacterSet(charactersIn: "/\\:\u{0}")
        name = name.components(separatedBy: illegal).joined(separator: "_")
        name = name.trimmingCharacters(in: .whitespacesAndNewlines)
        if name.isEmpty || name == "." || name == ".." {
            name = "download_\(Int(Date().timeIntervalSince1970))"
        }
        if name.count > 200 {
            name = String(name.suffix(200))
        }
        return name
    }

    private func cleanupFile(_ fileURL: URL) {
        try? FileManager.default.removeItem(at: fileURL)
    }
}
