import Foundation
import OpenClawKit
import os
import UIKit
import UniformTypeIdentifiers

final class ShareViewController: UIViewController {
    private struct ShareAttachment: Codable {
        var type: String
        var mimeType: String
        var fileName: String
        var content: String
    }

    private struct ExtractedShareContent {
        var payload: SharedContentPayload
        var attachments: [ShareAttachment]
    }

    private let logger = Logger(subsystem: "ai.openclaw.ios", category: "ShareExtension")
    private var statusLabel: UILabel?
    private let draftTextView = UITextView()
    private let sendButton = UIButton(type: .system)
    private let cancelButton = UIButton(type: .system)
    private var didPrepareDraft = false
    private var isSending = false
    private var pendingAttachments: [ShareAttachment] = []

    override func viewDidLoad() {
        super.viewDidLoad()
        self.preferredContentSize = CGSize(width: UIScreen.main.bounds.width, height: 420)
        self.setupUI()
    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        guard !self.didPrepareDraft else { return }
        self.didPrepareDraft = true
        Task { await self.prepareDraft() }
    }

    private func setupUI() {
        self.view.backgroundColor = .systemBackground

        self.draftTextView.translatesAutoresizingMaskIntoConstraints = false
        self.draftTextView.font = .preferredFont(forTextStyle: .body)
        self.draftTextView.backgroundColor = UIColor.secondarySystemBackground
        self.draftTextView.layer.cornerRadius = 10
        self.draftTextView.textContainerInset = UIEdgeInsets(top: 12, left: 10, bottom: 12, right: 10)

        self.sendButton.translatesAutoresizingMaskIntoConstraints = false
        self.sendButton.setTitle("Send to OpenClaw", for: .normal)
        self.sendButton.titleLabel?.font = .preferredFont(forTextStyle: .headline)
        self.sendButton.addTarget(self, action: #selector(self.handleSendTap), for: .touchUpInside)
        self.sendButton.isEnabled = false

        self.cancelButton.translatesAutoresizingMaskIntoConstraints = false
        self.cancelButton.setTitle("Cancel", for: .normal)
        self.cancelButton.addTarget(self, action: #selector(self.handleCancelTap), for: .touchUpInside)

        let buttons = UIStackView(arrangedSubviews: [self.cancelButton, self.sendButton])
        buttons.translatesAutoresizingMaskIntoConstraints = false
        buttons.axis = .horizontal
        buttons.alignment = .fill
        buttons.distribution = .fillEqually
        buttons.spacing = 12

        self.view.addSubview(self.draftTextView)
        self.view.addSubview(buttons)

        NSLayoutConstraint.activate([
            self.draftTextView.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor, constant: 14),
            self.draftTextView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 14),
            self.draftTextView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -14),
            self.draftTextView.bottomAnchor.constraint(equalTo: buttons.topAnchor, constant: -12),

            buttons.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 14),
            buttons.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -14),
            buttons.bottomAnchor.constraint(equalTo: self.view.keyboardLayoutGuide.topAnchor, constant: -8),
            buttons.heightAnchor.constraint(equalToConstant: 44),
        ])
    }

    private func prepareDraft() async {
        let traceId = UUID().uuidString
        ShareGatewayRelaySettings.saveLastEvent("Share opened.")
        self.showStatus("Preparing share…")
        self.logger.info("share begin trace=\(traceId, privacy: .public)")
        let extracted = await self.extractSharedContent()
        let payload = extracted.payload
        self.pendingAttachments = extracted.attachments
        self.logger.info(
            "share payload trace=\(traceId, privacy: .public) titleChars=\(payload.title?.count ?? 0) textChars=\(payload.text?.count ?? 0) hasURL=\(payload.url != nil) imageAttachments=\(self.pendingAttachments.count)"
        )
        let message = self.composeDraft(from: payload)
        await MainActor.run {
            self.draftTextView.text = message
            self.sendButton.isEnabled = true
            self.draftTextView.becomeFirstResponder()
        }
        if message.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
            ShareGatewayRelaySettings.saveLastEvent("Share ready: waiting for message input.")
            self.showStatus("Add a message, then tap Send.")
        } else {
            ShareGatewayRelaySettings.saveLastEvent("Share ready: draft prepared.")
            self.showStatus("Edit text, then tap Send.")
        }
    }

    @objc
    private func handleSendTap() {
        guard !self.isSending else { return }
        Task { await self.sendCurrentDraft() }
    }

    @objc
    private func handleCancelTap() {
        self.extensionContext?.completeRequest(returningItems: nil)
    }

    private func sendCurrentDraft() async {
        let message = await MainActor.run { self.draftTextView.text ?? "" }
        let trimmed = message.trimmingCharacters(in: .whitespacesAndNewlines)
        guard !trimmed.isEmpty else {
            ShareGatewayRelaySettings.saveLastEvent("Share blocked: message is empty.")
            self.showStatus("Message is empty.")
            return
        }

        await MainActor.run {
            self.isSending = true
            self.sendButton.isEnabled = false
            self.cancelButton.isEnabled = false
        }
        self.showStatus("Sending to OpenClaw gateway…")
        ShareGatewayRelaySettings.saveLastEvent("Sending to gateway…")
        do {
            try await self.sendMessageToGateway(trimmed, attachments: self.pendingAttachments)
            ShareGatewayRelaySettings.saveLastEvent(
                "Sent to gateway (\(trimmed.count) chars, \(self.pendingAttachments.count) attachment(s)).")
            self.showStatus("Sent to OpenClaw.")
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.45) {
                self.extensionContext?.completeRequest(returningItems: nil)
            }
        } catch {
            self.logger.error("share send failed reason=\(error.localizedDescription, privacy: .public)")
            ShareGatewayRelaySettings.saveLastEvent("Send failed: \(error.localizedDescription)")
            self.showStatus("Send failed: \(error.localizedDescription)")
            await MainActor.run {
                self.isSending = false
                self.sendButton.isEnabled = true
                self.cancelButton.isEnabled = true
            }
        }
    }

    private func sendMessageToGateway(_ message: String, attachments: [ShareAttachment]) async throws {
        guard let config = ShareGatewayRelaySettings.loadConfig() else {
            throw NSError(
                domain: "OpenClawShare",
                code: 10,
                userInfo: [NSLocalizedDescriptionKey: "OpenClaw is not connected to a gateway yet."])
        }
        guard let url = URL(string: config.gatewayURLString) else {
            throw NSError(
                domain: "OpenClawShare",
                code: 11,
                userInfo: [NSLocalizedDescriptionKey: "Invalid saved gateway URL."])
        }

        let gateway = GatewayNodeSession()
        defer {
            Task { await gateway.disconnect() }
        }
        let makeOptions: (String) -> GatewayConnectOptions = { clientId in
            GatewayConnectOptions(
                role: "node",
                scopes: [],
                caps: [],
                commands: [],
                permissions: [:],
                clientId: clientId,
                clientMode: "node",
                clientDisplayName: "OpenClaw Share",
                includeDeviceIdentity: false)
        }

        do {
            try await gateway.connect(
                url: url,
                token: config.token,
                password: config.password,
                connectOptions: makeOptions("openclaw-ios"),
                sessionBox: nil,
                onConnected: {},
                onDisconnected: { _ in },
                onInvoke: { req in
                    BridgeInvokeResponse(
                        id: req.id,
                        ok: false,
                        error: OpenClawNodeError(
                            code: .invalidRequest,
                            message: "share extension does not support node invoke"))
                })
        } catch {
            let expectsLegacyClientId = self.shouldRetryWithLegacyClientId(error)
            guard expectsLegacyClientId else { throw error }
            try await gateway.connect(
                url: url,
                token: config.token,
                password: config.password,
                connectOptions: makeOptions("moltbot-ios"),
                sessionBox: nil,
                onConnected: {},
                onDisconnected: { _ in },
                onInvoke: { req in
                    BridgeInvokeResponse(
                        id: req.id,
                        ok: false,
                        error: OpenClawNodeError(
                            code: .invalidRequest,
                            message: "share extension does not support node invoke"))
                })
        }

        struct AgentRequestPayload: Codable {
            var message: String
            var sessionKey: String?
            var thinking: String
            var deliver: Bool
            var attachments: [ShareAttachment]?
            var receipt: Bool
            var receiptText: String?
            var to: String?
            var channel: String?
            var timeoutSeconds: Int?
            var key: String?
        }

        let deliveryChannel = config.deliveryChannel?.trimmingCharacters(in: .whitespacesAndNewlines)
        let deliveryTo = config.deliveryTo?.trimmingCharacters(in: .whitespacesAndNewlines)
        let canDeliverToRoute = (deliveryChannel?.isEmpty == false) && (deliveryTo?.isEmpty == false)

        let params = AgentRequestPayload(
            message: message,
            sessionKey: config.sessionKey,
            thinking: "low",
            deliver: canDeliverToRoute,
            attachments: attachments.isEmpty ? nil : attachments,
            receipt: canDeliverToRoute,
            receiptText: canDeliverToRoute ? "Just received your iOS share + request, working on it." : nil,
            to: canDeliverToRoute ? deliveryTo : nil,
            channel: canDeliverToRoute ? deliveryChannel : nil,
            timeoutSeconds: nil,
            key: UUID().uuidString)
        let data = try JSONEncoder().encode(params)
        guard let json = String(data: data, encoding: .utf8) else {
            throw NSError(
                domain: "OpenClawShare",
                code: 12,
                userInfo: [NSLocalizedDescriptionKey: "Failed to encode chat payload."])
        }
        struct NodeEventParams: Codable {
            var event: String
            var payloadJSON: String
        }
        let eventData = try JSONEncoder().encode(NodeEventParams(event: "agent.request", payloadJSON: json))
        guard let nodeEventParams = String(data: eventData, encoding: .utf8) else {
            throw NSError(
                domain: "OpenClawShare",
                code: 13,
                userInfo: [NSLocalizedDescriptionKey: "Failed to encode node event payload."])
        }
        _ = try await gateway.request(method: "node.event", paramsJSON: nodeEventParams, timeoutSeconds: 25)
    }

    private func shouldRetryWithLegacyClientId(_ error: Error) -> Bool {
        if let gatewayError = error as? GatewayResponseError {
            let code = gatewayError.code.lowercased()
            let message = gatewayError.message.lowercased()
            let pathValue = (gatewayError.details["path"]?.value as? String)?.lowercased() ?? ""
            let mentionsClientIdPath =
                message.contains("/client/id") || message.contains("client id")
                || pathValue.contains("/client/id")
            let isInvalidConnectParams =
                (code.contains("invalid") && code.contains("connect"))
                || message.contains("invalid connect params")
            if isInvalidConnectParams && mentionsClientIdPath {
                return true
            }
        }

        let text = error.localizedDescription.lowercased()
        return text.contains("invalid connect params")
            && (text.contains("/client/id") || text.contains("client id"))
    }

    private func showStatus(_ text: String) {
        DispatchQueue.main.async {
            let label: UILabel
            if let existing = self.statusLabel {
                label = existing
            } else {
                let newLabel = UILabel()
                newLabel.translatesAutoresizingMaskIntoConstraints = false
                newLabel.numberOfLines = 0
                newLabel.textAlignment = .center
                newLabel.font = .preferredFont(forTextStyle: .body)
                newLabel.textColor = .label
                newLabel.backgroundColor = UIColor.systemBackground.withAlphaComponent(0.92)
                newLabel.layer.cornerRadius = 12
                newLabel.clipsToBounds = true
                newLabel.layoutMargins = UIEdgeInsets(top: 12, left: 14, bottom: 12, right: 14)
                self.view.addSubview(newLabel)
                NSLayoutConstraint.activate([
                    newLabel.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 18),
                    newLabel.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -18),
                    newLabel.bottomAnchor.constraint(equalTo: self.sendButton.topAnchor, constant: -10),
                ])
                self.statusLabel = newLabel
                label = newLabel
            }
            label.text = "  \(text)  "
        }
    }

    private func composeDraft(from payload: SharedContentPayload) -> String {
        var lines: [String] = []
        let title = self.sanitizeDraftFragment(payload.title)
        let text = self.sanitizeDraftFragment(payload.text)
        let url = payload.url?.absoluteString.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""

        if let title, !title.isEmpty { lines.append(title) }
        if let text, !text.isEmpty { lines.append(text) }
        if !url.isEmpty { lines.append(url) }

        return lines.joined(separator: "\n\n")
    }

    private func sanitizeDraftFragment(_ raw: String?) -> String? {
        guard let raw else { return nil }
        let banned = [
            "shared from ios.",
            "text:",
            "shared attachment(s):",
            "please help me with this.",
            "please help me with this.w",
        ]
        let cleanedLines = raw
            .components(separatedBy: .newlines)
            .map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
            .filter { line in
                guard !line.isEmpty else { return false }
                let lowered = line.lowercased()
                return !banned.contains { lowered == $0 || lowered.hasPrefix($0) }
            }
        let cleaned = cleanedLines.joined(separator: "\n").trimmingCharacters(in: .whitespacesAndNewlines)
        return cleaned.isEmpty ? nil : cleaned
    }

    private func extractSharedContent() async -> ExtractedShareContent {
        guard let items = self.extensionContext?.inputItems as? [NSExtensionItem] else {
            return ExtractedShareContent(
                payload: SharedContentPayload(title: nil, url: nil, text: nil),
                attachments: [])
        }

        var title: String?
        var sharedURL: URL?
        var sharedText: String?
        var imageCount = 0
        var videoCount = 0
        var fileCount = 0
        var unknownCount = 0
        var attachments: [ShareAttachment] = []
        let maxImageAttachments = 3

        for item in items {
            if title == nil {
                title = item.attributedTitle?.string ?? item.attributedContentText?.string
            }

            for provider in item.attachments ?? [] {
                if sharedURL == nil {
                    sharedURL = await self.loadURL(from: provider)
                }

                if sharedText == nil {
                    sharedText = await self.loadText(from: provider)
                }

                if provider.hasItemConformingToTypeIdentifier(UTType.image.identifier) {
                    imageCount += 1
                    if attachments.count < maxImageAttachments,
                       let attachment = await self.loadImageAttachment(from: provider, index: attachments.count)
                    {
                        attachments.append(attachment)
                    }
                } else if provider.hasItemConformingToTypeIdentifier(UTType.movie.identifier) {
                    videoCount += 1
                } else if provider.hasItemConformingToTypeIdentifier(UTType.fileURL.identifier) {
                    fileCount += 1
                } else {
                    unknownCount += 1
                }

            }
        }

        _ = imageCount
        _ = videoCount
        _ = fileCount
        _ = unknownCount

        return ExtractedShareContent(
            payload: SharedContentPayload(title: title, url: sharedURL, text: sharedText),
            attachments: attachments)
    }

    private func loadImageAttachment(from provider: NSItemProvider, index: Int) async -> ShareAttachment? {
        let imageUTI = self.preferredImageTypeIdentifier(from: provider) ?? UTType.image.identifier
        guard let rawData = await self.loadDataValue(from: provider, typeIdentifier: imageUTI) else {
            return nil
        }

        let maxBytes = 5_000_000
        guard let image = UIImage(data: rawData),
              let data = self.normalizedJPEGData(from: image, maxBytes: maxBytes)
        else {
            return nil
        }

        return ShareAttachment(
            type: "image",
            mimeType: "image/jpeg",
            fileName: "shared-image-\(index + 1).jpg",
            content: data.base64EncodedString())
    }

    private func preferredImageTypeIdentifier(from provider: NSItemProvider) -> String? {
        for identifier in provider.registeredTypeIdentifiers {
            guard let utType = UTType(identifier) else { continue }
            if utType.conforms(to: .image) {
                return identifier
            }
        }
        return nil
    }

    private func normalizedJPEGData(from image: UIImage, maxBytes: Int) -> Data? {
        var quality: CGFloat = 0.9
        while quality >= 0.4 {
            if let data = image.jpegData(compressionQuality: quality), data.count <= maxBytes {
                return data
            }
            quality -= 0.1
        }
        guard let fallback = image.jpegData(compressionQuality: 0.35) else { return nil }
        if fallback.count <= maxBytes { return fallback }
        return nil
    }

    private func loadURL(from provider: NSItemProvider) async -> URL? {
        if provider.hasItemConformingToTypeIdentifier(UTType.url.identifier) {
            if let url = await self.loadURLValue(
                from: provider,
                typeIdentifier: UTType.url.identifier)
            {
                return url
            }
        }

        if provider.hasItemConformingToTypeIdentifier(UTType.text.identifier) {
            if let text = await self.loadTextValue(from: provider, typeIdentifier: UTType.text.identifier),
               let url = URL(string: text.trimmingCharacters(in: .whitespacesAndNewlines)),
                   url.scheme != nil
            {
                return url
            }
        }

        return nil
    }

    private func loadText(from provider: NSItemProvider) async -> String? {
        if provider.hasItemConformingToTypeIdentifier(UTType.plainText.identifier) {
            if let text = await self.loadTextValue(from: provider, typeIdentifier: UTType.plainText.identifier) {
                return text
            }
        }

        if provider.hasItemConformingToTypeIdentifier(UTType.url.identifier) {
            if let url = await self.loadURLValue(from: provider, typeIdentifier: UTType.url.identifier) {
                return url.absoluteString
            }
        }

        return nil
    }

    private func loadURLValue(from provider: NSItemProvider, typeIdentifier: String) async -> URL? {
        await withCheckedContinuation { continuation in
            provider.loadItem(forTypeIdentifier: typeIdentifier, options: nil) { item, _ in
                if let url = item as? URL {
                    continuation.resume(returning: url)
                    return
                }
                if let str = item as? String, let url = URL(string: str) {
                    continuation.resume(returning: url)
                    return
                }
                if let ns = item as? NSString, let url = URL(string: ns as String) {
                    continuation.resume(returning: url)
                    return
                }
                continuation.resume(returning: nil)
            }
        }
    }

    private func loadTextValue(from provider: NSItemProvider, typeIdentifier: String) async -> String? {
        await withCheckedContinuation { continuation in
            provider.loadItem(forTypeIdentifier: typeIdentifier, options: nil) { item, _ in
                if let text = item as? String {
                    continuation.resume(returning: text)
                    return
                }
                if let text = item as? NSString {
                    continuation.resume(returning: text as String)
                    return
                }
                if let text = item as? NSAttributedString {
                    continuation.resume(returning: text.string)
                    return
                }
                continuation.resume(returning: nil)
            }
        }
    }

    private func loadDataValue(from provider: NSItemProvider, typeIdentifier: String) async -> Data? {
        await withCheckedContinuation { continuation in
            provider.loadDataRepresentation(forTypeIdentifier: typeIdentifier) { data, _ in
                continuation.resume(returning: data)
            }
        }
    }
}
