import Foundation
import WebKit

/// Uses a hidden WKWebView to execute fetch() calls with a real browser TLS fingerprint.
/// This bypasses Akamai bot detection that rejects URLSession requests.
@MainActor
final class WebViewFetcher {
    private var webView: WKWebView?

    func runJavaScript(
        navigateTo pageURL: String,
        userAgent: String? = nil,
        waitBeforeExecutingMs: UInt64 = 2000,
        script: String
    ) async -> (value: Any?, error: String?) {
        let webView = WKWebView(frame: .zero)
        self.webView = webView
        webView.customUserAgent = userAgent

        guard let url = URL(string: pageURL) else {
            return (nil, "Invalid page URL")
        }

        print("[WebViewFetcher] Navigating to \(pageURL)")
        let navResult = await withCheckedContinuation { (continuation: CheckedContinuation<Bool, Never>) in
            let delegate = NavigationDelegate(continuation: continuation)
            webView.navigationDelegate = delegate
            webView.load(URLRequest(url: url))
            objc_setAssociatedObject(webView, "navDelegate", delegate, .OBJC_ASSOCIATION_RETAIN)
        }

        guard navResult else {
            self.webView = nil
            return (nil, "Failed to load page")
        }

        try? await Task.sleep(for: .milliseconds(waitBeforeExecutingMs))
        let cookieNames = await currentCookieNames(for: webView)
        if !cookieNames.isEmpty {
            print("[WebViewFetcher] Cookies after navigation: \(cookieNames.sorted())")
        }

        do {
            let result = try await webView.callAsyncJavaScript(script, contentWorld: .page)
            self.webView = nil
            return (result, nil)
        } catch {
            print("[WebViewFetcher] callAsyncJavaScript error: \(error)")
            self.webView = nil
            return (nil, error.localizedDescription)
        }
    }

    /// Navigate to a domain to establish cookies/session, then execute a fetch from that context.
    func fetch(
        navigateTo pageURL: String,
        fetchURL: String,
        method: String = "POST",
        headers: [String: String] = [:],
        body: String? = nil,
        userAgent: String? = nil,
        includeCredentials: Bool = false
    ) async -> (data: String?, error: String?) {
        let js = """
        return await new Promise((resolve, reject) => {
            const xhr = new XMLHttpRequest();
            xhr.open("\(method)", "\(fetchURL)", true);
            xhr.withCredentials = \(includeCredentials ? "true" : "false");
            \(headers.map { "xhr.setRequestHeader(\"\($0.key)\", \"\($0.value)\");" }.joined(separator: "\n            "))
            xhr.onload = function() {
                resolve(JSON.stringify({ status: xhr.status, body: xhr.responseText }));
            };
            xhr.onerror = function() {
                resolve(JSON.stringify({ status: -1, body: "XHR error" }));
            };
            xhr.send(\(body ?? "null"));
        });
        """

        print("[WebViewFetcher] Executing fetch to \(fetchURL)")
        let result: (data: String?, error: String?)
        let evalResult = await runJavaScript(
            navigateTo: pageURL,
            userAgent: userAgent,
            waitBeforeExecutingMs: 2000,
            script: js
        )

        guard let jsValue = evalResult.value else {
            return (nil, evalResult.error ?? "JavaScript execution failed")
        }

        guard let resultStr = jsValue as? String else {
            print("[WebViewFetcher] Unexpected result type: \(type(of: jsValue))")
            return (nil, "No string result from JS")
        }

        if let data = resultStr.data(using: .utf8),
           let wrapper = try? JSONSerialization.jsonObject(with: data) as? [String: Any] {
            let status = wrapper["status"] as? Int ?? -1
            let body = wrapper["body"] as? String ?? ""
            print("[WebViewFetcher] Response status: \(status), body length: \(body.count)")
            if status == 200 {
                result = (body, nil)
            } else {
                result = (nil, "HTTP \(status): \(String(body.prefix(200)))")
            }
        } else {
            result = (resultStr, nil)
        }

        return result
    }

    private func currentCookieNames(for webView: WKWebView) async -> [String] {
        await withCheckedContinuation { continuation in
            webView.configuration.websiteDataStore.httpCookieStore.getAllCookies { cookies in
                continuation.resume(returning: cookies.map(\.name))
            }
        }
    }
}

// MARK: - Navigation Delegate

private class NavigationDelegate: NSObject, WKNavigationDelegate {
    private var continuation: CheckedContinuation<Bool, Never>?

    init(continuation: CheckedContinuation<Bool, Never>) {
        self.continuation = continuation
    }

    func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
        continuation?.resume(returning: true)
        continuation = nil
    }

    func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
        print("[WebViewFetcher] Navigation failed: \(error)")
        continuation?.resume(returning: false)
        continuation = nil
    }

    func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
        print("[WebViewFetcher] Provisional navigation failed: \(error)")
        continuation?.resume(returning: false)
        continuation = nil
    }
}
