import Foundation
import LilithLogging
import MacSyncShared

private let log = AppLogger.logger(for: "ICalls.Sync")

// MARK: - Stats

public struct ICallsSyncStats: Equatable, Sendable {
    public var callCount: Int = 0
    public var syncedThisSession: Int = 0
    public init() {}
}

// MARK: - Sync Error

public enum ICallsSyncError: Equatable, Sendable {
    case none
    case fullDiskAccessRequired
    case databaseNotFound
    case connectionFailed(String)

    public var message: String {
        switch self {
        case .none: return ""
        case .fullDiskAccessRequired: return "Full Disk Access required (Call History)"
        case .databaseNotFound: return "Call history database not found"
        case .connectionFailed(let m): return m
        }
    }
}

// MARK: - SyncManager

/// Orchestrates call history sync: read from CallHistory.storedata → push batches to server.
///
/// Read-only. 60s tick to keep calls "live" for inbox triage (matches the
/// iMessage read cadence); each cycle snapshots the WAL so recent calls show.
/// Watermark via BaseSyncManager lastSync (cycle time). First run backfills history.
/// Dedup on server via uniqueId (ZUNIQUE_ID or synthetic zpk:).
@MainActor
public final class SyncManager: BaseSyncManager<ICallsSyncStats, ICallsSyncError> {
    public static let shared = SyncManager()

    private let reader = CallHistoryReader.shared
    private let apiClient = APIClient.shared

    private let batchSize = 200

    private init() {
        super.init(
            initialStats: ICallsSyncStats(),
            noError: .none,
            persistenceKey: "icalls",
            timerInterval: 60
        )
    }

    public func forceFullResync() {
        guard !isSyncing else { return }
        log.info("Forcing full resync")
        setLastSync(nil)
        syncNow()
    }

    // MARK: - Authorization hooks (Full Disk Access for CallHistory.storedata)

    public override func isAuthorized() async -> Bool { reader.isAccessible }

    public override func requestAuthorization() async -> Bool {
        // Full Disk Access cannot be requested programmatically from the app.
        // The user must grant it in System Settings > Privacy & Security > Full Disk Access.
        // We re-probe; if still denied the base will call onAuthorizationDenied.
        return reader.isAccessible
    }

    public override func onAuthorizationDenied() {
        syncError = .fullDiskAccessRequired
    }

    // MARK: - Sync cycle

    public override func performSync() async {
        log.info("performSync starting")
        currentOperation = "Reading call history…"

        let watermark = lastSync
        let calls = await Task.detached(priority: .userInitiated) { [reader] in
            reader.fetchCalls(since: watermark)
        }.value

        log.info("Fetched \(calls.count) calls from history")

        guard !calls.isEmpty else {
            currentOperation = "No new calls"
            await fetchStats()
            return
        }

        currentOperation = "Syncing \(calls.count) calls…"
        var totalSynced = 0

        let batches = stride(from: 0, to: calls.count, by: batchSize).map {
            Array(calls[$0..<min($0 + batchSize, calls.count)])
        }

        for (idx, batch) in batches.enumerated() {
            currentOperation = "Syncing… (\(idx + 1)/\(batches.count))"

            let payloads = batch.map { c -> SyncCallPayload in
                SyncCallPayload(
                    uniqueId: c.uniqueId,
                    address: c.address,
                    normalizedAddress: c.normalizedAddress,
                    contactName: c.contactName,
                    direction: c.direction,
                    callType: c.callType,
                    answered: c.answered,
                    durationSeconds: c.durationSeconds,
                    startedAt: c.startedAt,
                    serviceProvider: c.serviceProvider
                )
            }

            do {
                let synced = try await apiClient.syncCalls(payloads)
                totalSynced += synced
                log.info("Batch \(idx + 1)/\(batches.count) synced=\(synced)")
                if syncError != .none && !SyncConnectionErrorHeuristic.isConnectionError(NSError(domain: "", code: 0)) {
                    syncError = .none
                }
            } catch {
                log.warning("Batch \(idx + 1) failed: \(error.localizedDescription)")
                if SyncConnectionErrorHeuristic.isConnectionError(error) {
                    syncError = .connectionFailed(error.localizedDescription)
                }
            }
        }

        stats.syncedThisSession = totalSynced

        await fetchStats()
        currentOperation = "Sync complete"
        log.info("performSync complete: synced=\(totalSynced)")
    }

    private func fetchStats() async {
        do {
            let response = try await apiClient.getStats()
            stats.callCount = response.totalCalls
            if let serverLastSync = response.lastSyncAt, lastSync == nil {
                setLastSync(serverLastSync)
            }
        } catch {
            log.warning("fetchStats failed: \(error.localizedDescription)")
        }
    }
}
