import AppKit
import Foundation
import GRDB
import LilithLogging
import MacSyncShared

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

/// Sends iMessages via AppleScript bridge to Messages.app
/// and checks delivery status from chat.db
@MainActor
class SendService {
    static let shared = SendService()

    private let activityLog = ActivityLog.shared

    private var dailySendCount: Int = 0
    private var hourlySendCount: Int = 0
    private var lastHourReset: Date = Date()
    private var lastDayReset: Date = Date()

    private let maxDailyMessages: Int = 150
    private let maxHourlyMessages: Int = 30

    private init() {}

    // MARK: - Validation (nonisolated for testability)

    nonisolated static func validate(recipient: String) -> (buddyId: String, isEmail: Bool, error: String?) {
        let isEmail = recipient.contains("@")

        if isEmail {
            let parts = recipient.split(separator: "@")
            guard parts.count == 2,
                  let domain = parts.last,
                  domain.contains(".") else {
                return ("", true, "Invalid email address format")
            }
            return (recipient, true, nil)
        } else {
            let cleaned = recipient.filter { $0.isNumber || $0 == "+" }
            guard cleaned.count >= 10 else {
                return ("", false, "Invalid phone number format")
            }
            return (cleaned, false, nil)
        }
    }

    nonisolated static func generateMessageId(buddyId: String, isEmail: Bool) -> String {
        let idSuffix: String
        if isEmail {
            let localPart = String(buddyId.split(separator: "@").first ?? "")
            idSuffix = String(localPart.suffix(4))
        } else {
            idSuffix = String(buddyId.suffix(4))
        }
        return "send_\(idSuffix)_\(Int(Date().timeIntervalSince1970))"
    }

    func send(recipient: String, body: String) -> (success: Bool, messageId: String?, error: String?) {
        resetCountersIfNeeded()

        if dailySendCount >= maxDailyMessages {
            return (false, nil, "Daily send limit reached (\(maxDailyMessages)/day)")
        }
        if hourlySendCount >= maxHourlyMessages {
            return (false, nil, "Hourly send limit reached (\(maxHourlyMessages)/hour)")
        }

        let validation = Self.validate(recipient: recipient)
        if let error = validation.error { return (false, nil, error) }

        let buddyId = validation.buddyId
        let isEmail = validation.isEmail
        let logPrefix = String(buddyId.prefix(4))

        let sanitizedBody = body
            .replacingOccurrences(of: "\\", with: "\\\\")
            .replacingOccurrences(of: "\"", with: "\\\"")

        let sanitizedBuddy = buddyId
            .replacingOccurrences(of: "\\", with: "\\\\")
            .replacingOccurrences(of: "\"", with: "\\\"")

        let script = """
        tell application "Messages"
            set targetService to 1st service whose service type = iMessage
            set targetBuddy to buddy "\(sanitizedBuddy)" of targetService
            send "\(sanitizedBody)" to targetBuddy
        end tell
        """

        let process = Process()
        process.executableURL = URL(fileURLWithPath: "/usr/bin/osascript")
        process.arguments = ["-e", script]

        let errorPipe = Pipe()
        process.standardError = errorPipe
        process.standardOutput = Pipe()

        do {
            try process.run()
            process.waitUntilExit()
        } catch {
            activityLog.error("iMessage send failed to \(logPrefix)...: \(error.localizedDescription)")
            return (false, nil, error.localizedDescription)
        }

        if process.terminationStatus != 0 {
            let errorData = errorPipe.fileHandleForReading.readDataToEndOfFile()
            let errorMessage = String(data: errorData, encoding: .utf8)?
                .trimmingCharacters(in: .whitespacesAndNewlines)
                ?? "osascript exited with code \(process.terminationStatus)"
            activityLog.error("iMessage send failed to \(logPrefix)...: \(errorMessage)")
            return (false, nil, errorMessage)
        }

        dailySendCount += 1
        hourlySendCount += 1

        let messageId = Self.generateMessageId(buddyId: buddyId, isEmail: isEmail)
        activityLog.success("iMessage sent to \(logPrefix)...")
        log.info("Message sent to \(logPrefix)..., id: \(messageId)")

        return (true, messageId, nil)
    }

    func checkDelivery(messageId: String) -> String {
        let parts = messageId.split(separator: "_")
        guard parts.count >= 3,
              let timestamp = Double(parts.last ?? "") else { return "pending" }

        let phoneSuffix = String(parts[1])
        let sentDate = Date(timeIntervalSince1970: timestamp)

        let reader = iMessageReader.shared
        guard let dbQueue = reader.getDatabaseQueue() else { return "pending" }

        do {
            return try dbQueue.read { db in
                let row = try Row.fetchOne(db, sql: """
                    SELECT m.date_delivered, m.date_read
                    FROM message m
                    JOIN chat_message_join cmj ON cmj.message_id = m.ROWID
                    JOIN chat_handle_join chj ON chj.chat_id = cmj.chat_id
                    JOIN handle h ON h.ROWID = chj.handle_id
                    WHERE m.is_from_me = 1
                      AND h.id LIKE ?
                      AND m.date > ?
                    ORDER BY m.date DESC
                    LIMIT 1
                    """, arguments: ["%\(phoneSuffix)", sentDate.timeIntervalSinceReferenceDate * 1_000_000_000])

                guard let row = row else { return "pending" }

                if row["date_read"] != nil && (row["date_read"] as? Int64 ?? 0) > 0 { return "read" }
                if row["date_delivered"] != nil && (row["date_delivered"] as? Int64 ?? 0) > 0 { return "delivered" }
                return "sent"
            }
        } catch {
            log.warning("Failed to check delivery: \(error)")
            return "pending"
        }
    }

    func getRateLimitStatus() -> (dailyRemaining: Int, hourlyRemaining: Int) {
        resetCountersIfNeeded()
        return (max(0, maxDailyMessages - dailySendCount), max(0, maxHourlyMessages - hourlySendCount))
    }

    private func resetCountersIfNeeded() {
        let now = Date()
        if now.timeIntervalSince(lastHourReset) >= 3600 {
            hourlySendCount = 0
            lastHourReset = now
        }
        if !Calendar.current.isDate(now, inSameDayAs: lastDayReset) {
            dailySendCount = 0
            lastDayReset = now
        }
    }
}
