import Alamofire
import Foundation
import LilithAgentCore
import LilithLogging
import MacSyncShared
import SwiftyJSON

private let log = AppLogger.logger(for: "IMessage.API")

// MARK: - Protocol

protocol APIClientProtocol: AnyObject, Sendable {
    var isAuthenticated: Bool { get }
    func syncMessages(_ payload: SyncMessagesPayload) async throws -> Int
    func syncMessagesBatch(_ payloads: [SyncMessagesPayload]) async throws -> Int
    func syncContacts(_ contacts: [SyncContactPayload]) async throws -> Int
    func getStats() async throws -> SyncStatsResponse
    func getPendingSends() async throws -> [PendingSendMessage]
    func reportSendResult(messageId: String, status: String, error: String?) async throws
    func resetSync() async throws -> ResetSyncResponse
}

// MARK: - APIClient

final class APIClient: BaseAPIClient, APIClientProtocol, @unchecked Sendable {
    static let shared: APIClient = {
        // Long timeouts for bulk initial-sync batches. Server does per-message
        // upserts, so a ~1500-message batch can take tens of seconds to commit.
        let config = URLSessionConfiguration.default
        config.timeoutIntervalForRequest = 300   // 5 min per request
        config.timeoutIntervalForResource = 900  // 15 min total including retries
        let session = Session(configuration: config)
        return APIClient(baseURL: macSyncResolveServerURL(), keychain: macSyncSharedKeychain, session: session)
    }()

    var isAuthenticated: Bool {
        (try? getAuthToken()) != nil
    }

    // MARK: - Message Sync

    func syncMessages(_ payload: SyncMessagesPayload) async throws -> Int {
        let data = try await authenticatedRequest(
            "/client/imessage/sync",
            method: .post,
            parameters: payload.dictionary
        )
        let json = JSON(data)
        log.info("syncMessages success=\(json["success"].boolValue) synced=\(json["data"]["synced"].intValue)")
        guard json["success"].boolValue else {
            let msg = json["error"]["message"].stringValue
            throw APIError.serverError(statusCode: json["statusCode"].intValue, message: msg.isEmpty ? "Server error" : msg)
        }
        return json["data"]["synced"].intValue
    }

    func syncMessagesBatch(_ payloads: [SyncMessagesPayload]) async throws -> Int {
        let params: [String: Any] = [
            "conversations": payloads.map { $0.dictionary }
        ]
        let data = try await authenticatedRequest(
            "/client/imessage/sync/batch",
            method: .post,
            parameters: params
        )
        let json = JSON(data)
        log.info("syncMessagesBatch success=\(json["success"].boolValue) synced=\(json["data"]["synced"].intValue)")
        guard json["success"].boolValue else {
            let msg = json["error"]["message"].stringValue
            throw APIError.serverError(statusCode: json["statusCode"].intValue, message: msg.isEmpty ? "Server error" : msg)
        }
        return json["data"]["synced"].intValue
    }

    // MARK: - Contacts

    func syncContacts(_ contacts: [SyncContactPayload]) async throws -> Int {
        let params: [String: Any] = ["contacts": contacts.map { $0.dictionary }]
        let data = try await authenticatedRequest("/client/imessage/contacts", method: .post, parameters: params)
        let json = JSON(data)
        guard json["success"].boolValue else {
            throw APIError.serverError(statusCode: 0, message: json["error"]["message"].stringValue)
        }
        return json["data"]["synced"].intValue
    }

    // MARK: - Stats

    func getStats() async throws -> SyncStatsResponse {
        let data = try await authenticatedRequest("/client/imessage/stats", method: .get)
        let json = JSON(data)
        guard json["success"].boolValue else {
            throw APIError.serverError(statusCode: 0, message: json["error"]["message"].stringValue)
        }
        return SyncStatsResponse(
            totalMessages: json["data"]["totalMessages"].intValue,
            totalConversations: json["data"]["totalConversations"].intValue,
            totalContacts: json["data"]["totalContacts"].intValue,
            lastSyncAt: json["data"]["lastSyncAt"].string.flatMap { ISO8601DateFormatter().date(from: $0) }
        )
    }

    // MARK: - Send Queue

    func getPendingSends() async throws -> [PendingSendMessage] {
        let data = try await authenticatedRequest("/client/imessage/send-queue/pending", method: .get)
        let json = JSON(data)
        guard json["success"].boolValue else {
            throw APIError.serverError(statusCode: 0, message: json["error"]["message"].stringValue)
        }
        return json["data"]["items"].arrayValue.map { msg in
            PendingSendMessage(
                id: msg["id"].stringValue,
                toHandle: msg["toHandle"].stringValue,
                body: msg["body"].stringValue,
                createdAt: msg["createdAt"].stringValue
            )
        }
    }

    func reportSendResult(messageId: String, status: String, error: String?) async throws {
        var params: [String: Any] = ["status": status]
        if let err = error { params["error"] = err }
        let data = try await authenticatedRequest("/client/imessage/send-queue/\(messageId)/result", method: .post, parameters: params)
        let json = JSON(data)
        guard json["success"].boolValue else {
            throw APIError.serverError(statusCode: 0, message: json["error"]["message"].stringValue)
        }
    }

    func resetSync() async throws -> ResetSyncResponse {
        let data = try await authenticatedRequest("/client/imessage/reset", method: .post)
        let json = JSON(data)
        guard json["success"].boolValue else {
            throw APIError.serverError(statusCode: 0, message: json["error"]["message"].stringValue)
        }
        return ResetSyncResponse(
            deletedMessages: json["data"]["deletedMessages"].intValue,
            deletedConversations: json["data"]["deletedConversations"].intValue
        )
    }
}

// MARK: - Payload Types

struct SyncMessagesPayload: Encodable {
    let conversationImessageId: String
    let conversationDisplayName: String
    let isGroup: Bool
    let participantIds: [String]
    let messages: [SyncMessagePayload]

    var dictionary: [String: Any] {
        [
            "conversationImessageId": conversationImessageId,
            "conversationDisplayName": conversationDisplayName,
            "isGroup": isGroup,
            "participantIds": participantIds,
            "messages": messages.map { $0.dictionary },
        ]
    }
}

struct SyncAttachmentPayload: Encodable {
    let filename: String?
    let mimeType: String?
    let transferName: String?
    let size: Int
    let data: String?

    var dictionary: [String: Any] {
        var d: [String: Any] = ["size": size]
        if let v = filename { d["filename"] = v }
        if let v = mimeType { d["mimeType"] = v }
        if let v = transferName { d["transferName"] = v }
        if let v = data { d["data"] = v }
        return d
    }
}

struct SyncMessagePayload: Encodable {
    let imessageGuid: String
    let senderId: String?
    let direction: String
    let sentAt: String
    let deliveredAt: String?
    let readAt: String?
    let text: String?
    let attributedBody: String?
    let associatedMessageType: Int?
    let associatedMessageGuid: String?
    let isAudioMessage: Bool
    let expressiveSendStyleId: String?
    let replyToGuid: String?
    let threadOriginatorGuid: String?
    let groupTitle: String?
    let balloonBundleId: String?
    let service: String?
    let senderIdentifier: String?
    let senderDisplayName: String?
    let senderPhoneNumber: String?
    let senderEmail: String?
    let attachments: [SyncAttachmentPayload]
    let attachmentsCount: Int
    let attachmentsTotalSize: Int
    let attachmentsFiletypes: [String]

    var dictionary: [String: Any] {
        var d: [String: Any] = [
            "imessageGuid": imessageGuid,
            "direction": direction,
            "sentAt": sentAt,
            "isAudioMessage": isAudioMessage,
            "attachments": attachments.map { $0.dictionary },
            "attachmentsCount": attachmentsCount,
            "attachmentsTotalSize": attachmentsTotalSize,
            "attachmentsFiletypes": attachmentsFiletypes,
        ]
        if let v = senderId { d["senderId"] = v }
        if let v = deliveredAt { d["deliveredAt"] = v }
        if let v = readAt { d["readAt"] = v }
        if let v = text { d["text"] = v }
        if let v = attributedBody { d["attributedBody"] = v }
        if let v = associatedMessageType { d["associatedMessageType"] = v }
        if let v = associatedMessageGuid { d["associatedMessageGuid"] = v }
        if let v = expressiveSendStyleId { d["expressiveSendStyleId"] = v }
        if let v = replyToGuid { d["replyToGuid"] = v }
        if let v = threadOriginatorGuid { d["threadOriginatorGuid"] = v }
        if let v = groupTitle { d["groupTitle"] = v }
        if let v = balloonBundleId { d["balloonBundleId"] = v }
        if let v = service { d["service"] = v }
        if let v = senderIdentifier { d["senderIdentifier"] = v }
        if let v = senderDisplayName { d["senderDisplayName"] = v }
        if let v = senderPhoneNumber { d["senderPhoneNumber"] = v }
        if let v = senderEmail { d["senderEmail"] = v }
        return d
    }
}

struct SyncStatsResponse {
    let totalMessages: Int
    let totalConversations: Int
    let totalContacts: Int
    let lastSyncAt: Date?
}

struct ResetSyncResponse {
    let deletedMessages: Int
    let deletedConversations: Int
}

struct SyncContactPayload: Encodable {
    let appleId: String?
    let phoneNumber: String?
    let email: String?
    let displayName: String
    let avatarHash: String?
    let birthday: String?

    var dictionary: [String: Any] {
        var d: [String: Any] = ["displayName": displayName]
        if let v = appleId { d["appleId"] = v }
        if let v = phoneNumber { d["phoneNumber"] = v }
        if let v = email { d["email"] = v }
        if let v = avatarHash { d["avatarHash"] = v }
        if let v = birthday { d["birthday"] = v }
        return d
    }
}

struct PendingSendMessage: Sendable {
    let id: String
    let toHandle: String
    let body: String
    let createdAt: String
}
