import Foundation
import EventKit

/// Scans the user's iOS calendars for events that look like flights and
/// returns parsed candidates. The user confirms each one in
/// `CalendarImportView` before anything lands in the log.
///
/// Detection is pattern-based on the event title — we look for any
/// `[A-Z]{2,3}\s*\d{1,4}` substring like "WN 7" / "SWA7" / "AA2178".
/// We also try to pull a route hint ("DFW → HOU") if the title or
/// notes carry one.
@MainActor
final class CalendarFlightImporter {
    let store: EKEventStore

    init(store: EKEventStore = EKEventStore()) {
        self.store = store
    }

    struct Candidate: Identifiable {
        let id = UUID()
        let event: EKEvent
        let carrierIATA: String?
        let flightNumber: String?
        let departureIATA: String?
        let arrivalIATA: String?
        var flightDate: Date { event.startDate }
        var flightLabel: String { "\(carrierIATA ?? "?")\(flightNumber ?? "?")" }
    }

    /// Request calendar access via the modern API.
    func requestAccess() async -> Bool {
        if #available(iOS 17.0, *) {
            do {
                return try await store.requestFullAccessToEvents()
            } catch {
                return false
            }
        } else {
            return await withCheckedContinuation { cont in
                store.requestAccess(to: .event) { granted, _ in
                    cont.resume(returning: granted)
                }
            }
        }
    }

    /// Scan all calendars between `from` and `to` for flight-shaped events.
    /// Default range: last 5 years through next 30 days, which is enough
    /// to catch most users' existing history without going overboard.
    func scan(
        from: Date = Calendar.current.date(byAdding: .year, value: -5, to: Date()) ?? Date(),
        to: Date = Calendar.current.date(byAdding: .day, value: 30, to: Date()) ?? Date()
    ) -> [Candidate] {
        // EventKit caps the search window — break it into yearly chunks
        // so we cover the full lookback even when the user has 5+ years
        // of calendar history.
        var out: [Candidate] = []
        var cursor = from
        let chunk: TimeInterval = 365 * 24 * 60 * 60
        while cursor < to {
            let end = min(cursor.addingTimeInterval(chunk), to)
            let predicate = store.predicateForEvents(withStart: cursor, end: end, calendars: nil)
            let events = store.events(matching: predicate)
            for e in events {
                if let c = parse(e) { out.append(c) }
            }
            cursor = end
        }
        return out
    }

    private func parse(_ event: EKEvent) -> Candidate? {
        let haystack = [event.title, event.notes, event.location]
            .compactMap { $0 }
            .joined(separator: " ")
        guard let match = matchFlightCode(in: haystack) else { return nil }
        let route = matchRoute(in: haystack)
        return Candidate(
            event: event,
            carrierIATA: match.carrier,
            flightNumber: match.number,
            departureIATA: route?.from,
            arrivalIATA: route?.to
        )
    }

    /// Find the first flight-code-shaped substring. Allows a single
    /// space between letters and digits (e.g. "WN 7", "AA 2178").
    private func matchFlightCode(in s: String) -> (carrier: String, number: String)? {
        let pattern = "([A-Z]{2,3})\\s*([0-9]{1,4})"
        guard let regex = try? NSRegularExpression(pattern: pattern) else { return nil }
        let range = NSRange(s.startIndex..., in: s)
        for m in regex.matches(in: s, range: range) where m.numberOfRanges == 3 {
            guard let cRange = Range(m.range(at: 1), in: s),
                  let nRange = Range(m.range(at: 2), in: s)
            else { continue }
            let carrier = String(s[cRange])
            // Filter false positives: skip common 2-letter codes that
            // aren't airlines but show up a lot in event titles.
            let denylist: Set<String> = ["AM", "PM", "ET", "PT", "CT", "MT", "US", "UK", "AS"]
            if denylist.contains(carrier) { continue }
            return (carrier, String(s[nRange]))
        }
        return nil
    }

    /// Find a "XXX → YYY" or "XXX-YYY" route hint.
    private func matchRoute(in s: String) -> (from: String, to: String)? {
        let pattern = "([A-Z]{3})\\s*(?:[-→>]|to)\\s*([A-Z]{3})"
        guard let regex = try? NSRegularExpression(pattern: pattern) else { return nil }
        let range = NSRange(s.startIndex..., in: s)
        guard let m = regex.firstMatch(in: s, range: range), m.numberOfRanges == 3,
              let fRange = Range(m.range(at: 1), in: s),
              let tRange = Range(m.range(at: 2), in: s)
        else { return nil }
        return (String(s[fRange]), String(s[tRange]))
    }
}
