import Foundation
import CoreLocation

// ---------------------------------------------------------------------------
// MARK: - RegoLookupService
// ---------------------------------------------------------------------------
/// A service that looks up vehicle registration details for a given license
/// plate.  Uses a free / demo API (or a simulated response when no API key is
/// configured) so the app is always usable during development.
///
/// **Production note**
/// Replace the `baseURL` and API key in `performLookup` with a real provider
/// (e.g. the UK DVLA, Australian RegoCheck, or US NHTSA endpoint).
final class RegoLookupService {

    // -----------------------------------------------------------------------
    // MARK: Types
    // -----------------------------------------------------------------------

    /// The data returned after a successful plate lookup.
    struct RegoResult: Codable, Equatable {
        let plate: String
        let make: String
        let model: String
        let year: Int
        let color: String
        let registeredState: String?
        let registeredOwner: String?
        let isStolen: Bool

        init(plate: String, make: String, model: String, year: Int,
             color: String, registeredState: String? = nil,
             registeredOwner: String? = nil, isStolen: Bool = false) {
            self.plate = plate
            self.make = make
            self.model = model
            self.year = year
            self.color = color
            self.registeredState = registeredState
            self.registeredOwner = registeredOwner
            self.isStolen = isStolen
        }
    }

    /// Errors the service can throw.
    enum LookupError: LocalizedError {
        case invalidPlate
        case networkFailure(underlying: Error)
        case notFound
        case rateLimited
        case noAPIKey

        var errorDescription: String? {
            switch self {
            case .invalidPlate:
                return "The plate format is invalid."
            case .networkFailure(let error):
                return "Network error: \(error.localizedDescription)"
            case .notFound:
                return "No registration found for this plate."
            case .rateLimited:
                return "Too many requests. Please try again later."
            case .noAPIKey:
                return "No API key configured for registration lookup."
            }
        }
    }

    // -----------------------------------------------------------------------
    // MARK: Singleton
    // -----------------------------------------------------------------------

    static let shared = RegoLookupService()
    private init() {}

    // -----------------------------------------------------------------------
    // MARK: Configuration
    // -----------------------------------------------------------------------

    /// Set this to a real API key in production.
    var apiKey: String?

    /// The base URL of the registration API.
    private let baseURL = "https://api.example.com/v1/rego"

    /// Simple in-memory rate‑limiter: max 10 requests per 60 seconds.
    private var requestTimestamps: [Date] = []
    private let maxRequests = 10
    private let rateLimitWindow: TimeInterval = 60

    // -----------------------------------------------------------------------
    // MARK: Public API
    // -----------------------------------------------------------------------

    /// Looks up a licence plate and returns the registration details.
    ///
    /// - Parameter plate: The licence plate string (e.g. "7ABC123").
    ///   The service normalises it (uppercase, no spaces) before sending.
    /// - Returns: A `RegoResult` on success.
    /// - Throws: `LookupError` on failure.
    func lookup(plate: String) async throws -> RegoResult {
        let normalised = normalise(plate)

        guard !normalised.isEmpty else {
            throw LookupError.invalidPlate
        }

        // --- Rate limiter ---
        try checkRateLimit()

        // --- Simulated / demo path (no API key) ---
        guard apiKey != nil else {
            return try await simulatedLookup(plate: normalised)
        }

        // --- Real API path ---
        return try await performLookup(plate: normalised)
    }

    // -----------------------------------------------------------------------
    // MARK: Private API
    // -----------------------------------------------------------------------

    /// Normalises a plate string: uppercase, no whitespace.
    private func normalise(_ raw: String) -> String {
        raw
            .trimmingCharacters(in: .whitespacesAndNewlines)
            .uppercased()
            .components(separatedBy: .whitespaces)
            .joined()
    }

    /// Checks the rate‑limit window and throws `LookupError.rateLimited`
    /// if the caller has exceeded `maxRequests` in the last `rateLimitWindow`
    /// seconds.
    private func checkRateLimit() throws {
        let now = Date()
        // Remove timestamps older than the window.
        requestTimestamps.removeAll { now.timeIntervalSince($0) > rateLimitWindow }

        guard requestTimestamps.count < maxRequests else {
            throw LookupError.rateLimited
        }

        requestTimestamps.append(now)
    }

    /// Performs the actual HTTP request to the registration API.
    ///
    /// Override this method (or subclass) to call a real endpoint.
    private func performLookup(plate: String) async throws -> RegoResult {
        guard let key = apiKey else {
            throw LookupError.noAPIKey
        }

        var components = URLComponents(string: baseURL)
        components?.queryItems = [
            URLQueryItem(name: "plate", value: plate),
            URLQueryItem(name: "api_key", value: key)
        ]

        guard let url = components?.url else {
            throw LookupError.invalidPlate
        }

        var request = URLRequest(url: url)
        request.httpMethod = "GET"
        request.setValue("application/json", forHTTPHeaderField: "Accept")
        request.timeoutInterval = 15

        let (data, response): (Data, URLResponse)
        do {
            (data, response) = try await URLSession.shared.data(for: request)
        } catch {
            throw LookupError.networkFailure(underlying: error)
        }

        guard let httpResponse = response as? HTTPURLResponse else {
            throw LookupError.networkFailure(
                underlying: URLError(.badServerResponse)
            )
        }

        switch httpResponse.statusCode {
        case 200:
            let decoder = JSONDecoder()
            do {
                return try decoder.decode(RegoResult.self, from: data)
            } catch {
                throw LookupError.networkFailure(underlying: error)
            }
        case 404:
            throw LookupError.notFound
        case 429:
            throw LookupError.rateLimited
        default:
            throw LookupError.networkFailure(
                underlying: URLError(.init(rawValue: httpResponse.statusCode))
            )
        }
    }

    // -----------------------------------------------------------------------
    // MARK: Simulated lookup (demo / offline)
    // -----------------------------------------------------------------------

    /// Returns simulated data for well‑known test plates so the UI is always
    /// interactable.  In a real app this branch is removed and `apiKey` is
    /// set.
    private func simulatedLookup(plate: String) async throws -> RegoResult {
        // Simulate network latency.
        try await Task.sleep(nanoseconds: 600_000_000) // 0.6 s

        // A small dictionary of test plates so the user can try the feature.
        let samples: [String: RegoResult] = [
            "7ABC123": RegoResult(
                plate: "7ABC123",
                make: "Tesla",
                model: "Model 3",
                year: 2024,
                color: "Deep Blue Metallic",
                registeredState: "CA",
                registeredOwner: "Davey"
            ),
            "1XYZ789": RegoResult(
                plate: "1XYZ789",
                make: "Ford",
                model: "Mustang GT",
                year: 2023,
                color: "Race Red",
                registeredState: "CA"
            ),
            "3QWE456": RegoResult(
                plate: "3QWE456",
                make: "Toyota",
                model: "GR Corolla",
                year: 2024,
                color: "White",
                registeredState: "NV"
            )
        ]

        if let result = samples[plate] {
            return result
        }

        // Unknown plate — return a plausible guess based on common patterns.
        throw LookupError.notFound
    }
}

// ===========================================================================
// MARK: - Convenience extension
// ===========================================================================

extension RegoLookupService {
    /// Returns `true` when the plate passes basic structural checks (length,
    /// allowed characters).  Useful for enabling/disabling the lookup button.
    static func isValidPlateFormat(_ plate: String) -> Bool {
        let cleaned = plate
            .trimmingCharacters(in: .whitespacesAndNewlines)
            .uppercased()
            .components(separatedBy: .whitespaces)
            .joined()

        guard (2 ... 10).contains(cleaned.count) else { return false }

        // Allow letters, digits, and hyphens.
        let allowed = CharacterSet.alphanumerics.union(CharacterSet(charactersIn: "-"))
        return CharacterSet(charactersIn: cleaned).isSubset(of: allowed)
    }
}
