import Foundation

/// Aircraft photo lookup, backed by planespotters.net's public API.
///
/// Two lookup paths, tried in order:
///   1. By registration / tail number (e.g. "N971NN") — preferred, since
///      FR24's feed gives us this inline.
///   2. By 24-bit ICAO transponder hex (e.g. "ad8895") — fallback when
///      we don't have a registration (most GA / military aircraft).
///
/// Planespotters serves the most recent photo of that airframe — which
/// naturally surfaces special liveries, since photographers prioritize
/// catching one-off paint schemes the moment they appear.
///
/// Their TOS requires:
///   - A User-Agent with a contact URL.
///   - Photographer attribution wherever the photo is shown.
/// Both are honored here.
actor AircraftPhotoService {
    static let shared = AircraftPhotoService()

    struct Photo: Hashable, Sendable {
        let thumbnailURL: URL    // ~200×112
        let largeURL: URL        // ~497×280
        let detailLink: URL?     // planespotters page (attribution requires linking)
        let photographer: String?
    }

    private var cache: [String: Photo?] = [:]
    private let session: URLSession

    private init(session: URLSession = .shared) {
        self.session = session
    }

    /// Returns the most recent photo for an airframe, or nil if none.
    /// Results (hits AND misses) are cached for the lifetime of the app
    /// so we never re-hit planespotters for the same airframe twice.
    func photo(registration: String?, icao24: String) async -> Photo? {
        let key = (registration?.uppercased())
            ?? icao24.uppercased()
        if let cached = cache[key] { return cached }

        // 1) Try registration. Planespotters indexes by tail number and
        //    most operators are accurately mapped.
        if let reg = registration?.uppercased(), !reg.isEmpty {
            if let p = await fetch(path: "reg/\(reg)") {
                cache[key] = p
                return p
            }
        }
        // 2) Fall back to ICAO24 hex — slower index but catches some
        //    airframes that don't have a current registration on file.
        let hexKey = icao24.uppercased()
        if let p = await fetch(path: "hex/\(hexKey)") {
            cache[key] = p
            return p
        }
        // Cache the miss so we don't re-query.
        cache[key] = .some(nil)
        return nil
    }

    private func fetch(path: String) async -> Photo? {
        guard let url = URL(string: "https://api.planespotters.net/pub/photos/\(path)") else {
            return nil
        }
        var req = URLRequest(url: url)
        req.timeoutInterval = 8
        // Planespotters' TOS requires a contact URL in the User-Agent
        // string. Without it the API returns an error blob.
        req.setValue(
            "Flights/1.0 (+https://github.com/admin/Flights)",
            forHTTPHeaderField: "User-Agent"
        )
        req.setValue("application/json", forHTTPHeaderField: "Accept")

        do {
            let (data, resp) = try await session.data(for: req)
            guard let http = resp as? HTTPURLResponse, (200..<300).contains(http.statusCode) else {
                return nil
            }
            guard let root = try JSONSerialization.jsonObject(with: data) as? [String: Any],
                  let photos = root["photos"] as? [[String: Any]],
                  let first = photos.first
            else { return nil }
            return Self.parse(first)
        } catch {
            return nil
        }
    }

    private static func parse(_ row: [String: Any]) -> Photo? {
        guard let thumbObj = row["thumbnail_large"] as? [String: Any] ?? row["thumbnail"] as? [String: Any],
              let thumbSrc = thumbObj["src"] as? String,
              let thumbURL = URL(string: thumbSrc)
        else { return nil }

        let large: URL = {
            if let lg = row["thumbnail_large"] as? [String: Any],
               let s = lg["src"] as? String,
               let u = URL(string: s) { return u }
            return thumbURL
        }()

        let link = (row["link"] as? String).flatMap(URL.init(string:))
        let photographer = (row["photographer"] as? String)
            .flatMap { $0.isEmpty ? nil : $0 }

        return Photo(
            thumbnailURL: thumbURL,
            largeURL: large,
            detailLink: link,
            photographer: photographer
        )
    }
}
