import Foundation

struct AuthRepository {
    let client: APIClient
    let session: SessionStore
    let config: APIConfig

    /// Register (creates a pending user if new, resends otherwise) and dispatch the OTP.
    func requestCode(phone: String) async throws {
        try await client.postNoContent(
            config.auth.appendingPathComponent("api/v1/auth/register"),
            PhoneRequest(phone: phone)
        )
    }

    /// Verify the OTP; on success the JWT + userId are persisted to the session.
    func verify(phone: String, code: String) async throws {
        let auth: AuthResponse = try await client.post(
            config.auth.appendingPathComponent("api/v1/auth/register/verify"),
            VerifyOtpRequest(phone: phone, code: code)
        )
        session.save(token: auth.accessToken, userId: auth.userId, refreshToken: auth.refreshToken)
    }
}

struct RideRepository {
    let client: APIClient
    let config: APIConfig

    func nearby(lat: Double, lng: Double) async throws -> [NearbyScooter] {
        var components = URLComponents(
            url: config.fleet.appendingPathComponent("api/v1/scooters/nearby"),
            resolvingAgainstBaseURL: false
        )!
        components.queryItems = [
            URLQueryItem(name: "lat", value: String(lat)),
            URLQueryItem(name: "lng", value: String(lng)),
        ]
        return try await client.get(components.url!)
    }

    func startRide(qrCode: String, lat: Double, lng: Double) async throws -> RideResponse {
        try await client.post(
            config.booking.appendingPathComponent("api/v1/rides"),
            AllocateRideRequest(qrCode: qrCode, lat: lat, lng: lng)
        )
    }

    func ride(id: String) async throws -> RideResponse {
        try await client.get(config.booking.appendingPathComponent("api/v1/rides/\(id)"))
    }

    func endRide(id: String, lat: Double, lng: Double, photoUrl: String) async throws -> InvoiceResponse {
        try await client.post(
            config.booking.appendingPathComponent("api/v1/rides/\(id)/end"),
            EndRideRequest(lat: lat, lng: lng, photoUrl: photoUrl)
        )
    }

    /// The rider's rides, newest first. X-User-Id is added by the client.
    func history() async throws -> [RideResponse] {
        try await client.get(config.booking.appendingPathComponent("api/v1/rides"))
    }
}

struct WalletRepository {
    let client: APIClient
    let session: SessionStore
    let config: APIConfig

    func balance() async throws -> WalletResponse {
        guard let userId = session.userId else { throw APIError.http(401, "Not signed in") }
        return try await client.get(config.wallet.appendingPathComponent("api/v1/wallets/\(userId)"))
    }

    func topUp(packageCode: String) async throws -> PaymentResponse {
        guard let userId = session.userId else { throw APIError.http(401, "Not signed in") }
        return try await client.post(
            config.payment.appendingPathComponent("api/v1/payments/topups"),
            TopupRequest(userId: userId, packageCode: packageCode, amount: nil)
        )
    }
}


/// The privacy policy and terms a rider must accept before riding.
///
/// booking-service enforces this too (451 on allocate), so a failure here is not fatal: the app
/// asks, and the server is the one that refuses. That is why `outstanding()` swallows errors
/// rather than blocking a rider out of their ride over an unreachable auth-service.
struct LegalRepository {
    let client: APIClient
    let config: APIConfig

    func outstanding() async -> [LegalDocument] {
        (try? await client.get(config.auth.appendingPathComponent("api/v1/legal/outstanding"))) ?? []
    }

    /// Every document in force, regardless of who is asking.
    ///
    /// Used when booking-service refuses with 451: the server has told us agreement is missing,
    /// and this endpoint needs no token, so the step can still be shown when `outstanding()`
    /// could not say which documents were pending.
    func documents() async -> [LegalDocument] {
        (try? await client.get(config.auth.appendingPathComponent("api/v1/legal/documents"))) ?? []
    }

    func accept(_ document: LegalDocument) async throws {
        try await client.postNoContent(
            config.auth.appendingPathComponent("api/v1/legal/consents"),
            AcceptConsentRequest(document: document.key, version: document.version)
        )
    }
}
