import EventKit
import Foundation

// MARK: - CalendarEventStore
// Speichert die Zuordnung Moment-UUID → EKEvent-Identifier in UserDefaults.
// Dieser Mapping-Store ist bewusst device-lokal (EKEvent-IDs sind nicht cloud-sync-fähig).

enum CalendarEventStore {
    private static let key = "nahbar.momentCalendarEvents"

    /// Feuert wenn sich das Mapping ändert. Das `object` ist die betroffene Moment-UUID.
    static let didChangeNotification = Notification.Name("CalendarEventStoreDidChange")

    static func save(momentID: UUID, eventIdentifier: String) {
        var map = load()
        map[momentID.uuidString] = eventIdentifier
        UserDefaults.standard.set(map, forKey: key)
        DispatchQueue.main.async {
            NotificationCenter.default.post(name: didChangeNotification, object: momentID)
        }
    }

    static func identifier(for momentID: UUID) -> String? {
        load()[momentID.uuidString]
    }

    static func remove(momentID: UUID) {
        var map = load()
        map.removeValue(forKey: momentID.uuidString)
        UserDefaults.standard.set(map, forKey: key)
        DispatchQueue.main.async {
            NotificationCenter.default.post(name: didChangeNotification, object: momentID)
        }
    }

    private static func load() -> [String: String] {
        UserDefaults.standard.dictionary(forKey: key) as? [String: String] ?? [:]
    }
}

// MARK: - CalendarManager

final class CalendarManager {
    static let shared = CalendarManager()
    private init() {}

    private let store = EKEventStore()

    // MARK: Berechtigungen

    /// Fordert Full Access an und gibt zurück, ob Zugriff gewährt wurde.
    func requestFullAccess() async -> Bool {
        do {
            if #available(iOS 17.0, *) {
                return try await store.requestFullAccessToEvents()
            } else {
                return try await store.requestAccess(to: .event)
            }
        } catch {
            return false
        }
    }

    /// Schreibt einen neuen Kalender-Eintrag und gibt den EKEvent-Identifier zurück.
    /// Gibt nil zurück, wenn kein Zugriff gewährt wurde oder ein Fehler auftritt.
    /// `alarmOffset`: negativer Zeitabstand vor dem Start in Sekunden (nil = keine Erinnerung).
    func createEvent(
        title: String,
        notes: String?,
        startDate: Date,
        endDate: Date,
        isAllDay: Bool,
        calendarIdentifier: String?,
        alarmOffset: TimeInterval? = nil
    ) async -> String? {
        let granted = await requestFullAccess()
        guard granted else { return nil }

        let calendar: EKCalendar? = {
            if let id = calendarIdentifier {
                return store.calendar(withIdentifier: id)
            }
            return store.defaultCalendarForNewEvents
        }()
        guard let calendar else { return nil }

        let event = EKEvent(eventStore: store)
        event.title = title
        event.isAllDay = isAllDay
        event.startDate = startDate
        event.endDate = endDate
        event.calendar = calendar

        // nahbar-Markierung in den Notizen
        let marker = "— via nahbar"
        if let existingNotes = notes, !existingNotes.isEmpty {
            event.notes = "\(existingNotes)\n\n\(marker)"
        } else {
            event.notes = marker
        }

        // Erinnerung (EKAlarm) wenn gewünscht
        if let offset = alarmOffset {
            event.addAlarm(EKAlarm(relativeOffset: offset))
        }

        do {
            try store.save(event, span: .thisEvent)
            return event.eventIdentifier
        } catch {
            return nil
        }
    }

    /// Aktualisiert Titel, Notizen und Startdatum eines bestehenden Kalendereintrags.
    /// Die Dauer sowie Ganztages-/Alarm-Einstellungen des Eintrags bleiben erhalten.
    /// Gibt true zurück, wenn der Eintrag gefunden und gespeichert wurde.
    func updateEvent(identifier: String, title: String, notes: String?, newStartDate: Date) async -> Bool {
        let granted = await requestFullAccess()
        guard granted else { return false }
        guard let event = store.event(withIdentifier: identifier) else { return false }

        event.title = title

        let marker = "— via nahbar"
        if let n = notes, !n.isEmpty {
            event.notes = "\(n)\n\n\(marker)"
        } else {
            event.notes = marker
        }

        // Startdatum verschieben, Dauer beibehalten (nur für zeitgebundene Einträge)
        if !event.isAllDay {
            let duration = event.endDate.timeIntervalSince(event.startDate)
            event.startDate = newStartDate
            event.endDate = newStartDate.addingTimeInterval(duration)
        }

        do {
            try store.save(event, span: .thisEvent)
            return true
        } catch {
            return false
        }
    }

    /// Löscht einen Kalendereintrag anhand seines Identifiers.
    /// Gibt true zurück, wenn der Eintrag gefunden und gelöscht wurde.
    func deleteEvent(identifier: String) async -> Bool {
        let granted = await requestFullAccess()
        guard granted else { return false }

        guard let event = store.event(withIdentifier: identifier) else { return false }
        do {
            try store.remove(event, span: .thisEvent)
            return true
        } catch {
            return false
        }
    }

    /// Gibt alle Benutzer-Kalender zurück (sortiert nach Titel).
    func availableCalendars() async -> [EKCalendar] {
        let granted = await requestFullAccess()
        guard granted else { return [] }
        return store.calendars(for: .event)
            .filter { $0.allowsContentModifications }
            .sorted { $0.title < $1.title }
    }

    /// Gibt den Standardkalender für neue Einträge zurück.
    var defaultCalendarIdentifier: String? {
        store.defaultCalendarForNewEvents?.calendarIdentifier
    }
}
