import EventKit
import Foundation
import LilithLogging
import MacSyncShared

private let log = AppLogger.logger(for: "IReminder.Sender")

// MARK: - Wire types

public struct ReminderSendPayload: Sendable, Decodable, Equatable {
    public let reminderIdentifier: String?
    public let calendarIdentifier: String?
    public let title: String?
    public let notes: String?
    public let dueDate: String?
    public let priority: Int?
    public let isCompleted: Bool?

    public init(
        reminderIdentifier: String? = nil,
        calendarIdentifier: String? = nil,
        title: String? = nil,
        notes: String? = nil,
        dueDate: String? = nil,
        priority: Int? = nil,
        isCompleted: Bool? = nil
    ) {
        self.reminderIdentifier = reminderIdentifier
        self.calendarIdentifier = calendarIdentifier
        self.title = title
        self.notes = notes
        self.dueDate = dueDate
        self.priority = priority
        self.isCompleted = isCompleted
    }
}

public struct PendingReminderSend: Sendable, Decodable, Equatable {
    public let id: String
    public let action: String
    public let payload: ReminderSendPayload
    public let createdAt: String

    public init(id: String, action: String, payload: ReminderSendPayload, createdAt: String) {
        self.id = id
        self.action = action
        self.payload = payload
        self.createdAt = createdAt
    }
}

// MARK: - Applier protocol (allows hermetic tests without touching EKEventStore)

/// Narrow protocol the `ReminderSender` calls into so tests can substitute a
/// fake that does not require EventKit authorization.
@MainActor
public protocol ReminderApplying {
    func calendar(withIdentifier id: String) -> EKCalendar?
    func reminder(withIdentifier id: String) -> EKReminder?
    func makeReminder() -> EKReminder
    func save(_ reminder: EKReminder) throws
    func remove(_ reminder: EKReminder) throws
}

/// Default `ReminderApplying` backed by a real `EKEventStore`.
@MainActor
public final class EKReminderStoreApplier: ReminderApplying {
    private let store: EKEventStore
    public init(eventStore: EKEventStore) { self.store = eventStore }

    public func calendar(withIdentifier id: String) -> EKCalendar? {
        store.calendar(withIdentifier: id)
    }

    public func reminder(withIdentifier id: String) -> EKReminder? {
        store.calendarItem(withIdentifier: id) as? EKReminder
    }

    public func makeReminder() -> EKReminder {
        EKReminder(eventStore: store)
    }

    public func save(_ reminder: EKReminder) throws {
        try store.save(reminder, commit: true)
    }

    public func remove(_ reminder: EKReminder) throws {
        try store.remove(reminder, commit: true)
    }
}

// MARK: - Sender

/// Applies pending reminder send-queue items against the local EventKit store.
@MainActor
public final class ReminderSender {
    private let applier: any ReminderApplying
    private let iso: ISO8601DateFormatter

    public init(applier: any ReminderApplying) {
        self.applier = applier
        let f = ISO8601DateFormatter()
        f.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
        self.iso = f
    }

    public convenience init(eventStore: EKEventStore) {
        self.init(applier: EKReminderStoreApplier(eventStore: eventStore))
    }

    public func apply(_ item: PendingReminderSend) async -> SendQueueApplyResult {
        log.info("apply id=\(item.id) action=\(item.action)")
        switch item.action {
        case "create_reminder": return applyCreate(item.payload)
        case "update_reminder": return applyUpdate(item.payload)
        case "delete_reminder": return applyDelete(item.payload)
        default:
            return .failed(reason: "unknown action: \(item.action)")
        }
    }

    // MARK: - create

    private func applyCreate(_ p: ReminderSendPayload) -> SendQueueApplyResult {
        guard let calId = p.calendarIdentifier, !calId.isEmpty else {
            return .failed(reason: "missing calendarIdentifier")
        }
        guard let title = p.title, !title.isEmpty else {
            return .failed(reason: "missing title")
        }
        guard let calendar = applier.calendar(withIdentifier: calId) else {
            return .failed(reason: "calendar not found: \(calId)")
        }

        let reminder = applier.makeReminder()
        reminder.calendar = calendar
        reminder.title = title
        if let notes = p.notes { reminder.notes = notes }
        if let prio = p.priority { reminder.priority = prio }
        if let comp = p.isCompleted { reminder.isCompleted = comp }
        if let dueStr = p.dueDate {
            guard let due = parseDate(dueStr) else {
                return .failed(reason: "invalid dueDate")
            }
            reminder.dueDateComponents = Calendar.current.dateComponents(
                [.year, .month, .day, .hour, .minute],
                from: due
            )
        }

        do {
            try applier.save(reminder)
            return .sent
        } catch {
            return .failed(reason: "save failed: \(error.localizedDescription)")
        }
    }

    // MARK: - update

    private func applyUpdate(_ p: ReminderSendPayload) -> SendQueueApplyResult {
        guard let reminderId = p.reminderIdentifier, !reminderId.isEmpty else {
            return .failed(reason: "missing reminderIdentifier")
        }
        guard let reminder = applier.reminder(withIdentifier: reminderId) else {
            return .failed(reason: "reminder not found: \(reminderId)")
        }
        if let title = p.title { reminder.title = title }
        if let notes = p.notes { reminder.notes = notes }
        if let prio = p.priority { reminder.priority = prio }
        if let comp = p.isCompleted { reminder.isCompleted = comp }
        if let dueStr = p.dueDate {
            guard let due = parseDate(dueStr) else {
                return .failed(reason: "invalid dueDate")
            }
            reminder.dueDateComponents = Calendar.current.dateComponents(
                [.year, .month, .day, .hour, .minute],
                from: due
            )
        }

        do {
            try applier.save(reminder)
            return .sent
        } catch {
            return .failed(reason: "save failed: \(error.localizedDescription)")
        }
    }

    // MARK: - delete

    private func applyDelete(_ p: ReminderSendPayload) -> SendQueueApplyResult {
        guard let reminderId = p.reminderIdentifier, !reminderId.isEmpty else {
            return .failed(reason: "missing reminderIdentifier")
        }
        guard let reminder = applier.reminder(withIdentifier: reminderId) else {
            return .failed(reason: "reminder not found: \(reminderId)")
        }
        do {
            try applier.remove(reminder)
            return .sent
        } catch {
            return .failed(reason: "remove failed: \(error.localizedDescription)")
        }
    }

    // MARK: - date parsing

    /// Parse ISO8601 with or without fractional seconds.
    private func parseDate(_ s: String) -> Date? {
        if let d = iso.date(from: s) { return d }
        let f = ISO8601DateFormatter()
        f.formatOptions = [.withInternetDateTime]
        return f.date(from: s)
    }
}

// MARK: - SendQueueTransport adapter

public struct ReminderSendTransport: SendQueueTransport {
    public typealias PendingItem = PendingReminderSend

    private let apiClient: any IReminderAPIClientProtocol

    public init(apiClient: any IReminderAPIClientProtocol) {
        self.apiClient = apiClient
    }

    public func id(of item: PendingReminderSend) -> String { item.id }

    public func fetchPending() async throws -> [PendingReminderSend] {
        try await apiClient.getPendingSends()
    }

    public func reportResult(id: String, status: String, error: String?) async throws {
        try await apiClient.reportSendResult(id: id, status: status, error: error)
    }
}
