import Foundation

enum APIConfig {
    #if DEBUG
    static let baseURL = URL(string: "http://127.0.0.1:8000/api/")!
    #else
    static let baseURL = URL(string: "https://notifyhub.example.com/api/")!
    #endif
}

final class APIClient {
    static let shared = APIClient()

    private let session: URLSession
    private let decoder = JSONDecoder()
    private let encoder = JSONEncoder()

    private init() {
        let config = URLSessionConfiguration.default
        config.timeoutIntervalForRequest = 30
        // Release: enforce HTTPS via ATS (Info.plist). Debug allows local networking.
        session = URLSession(configuration: config)
    }

    // MARK: - Auth

    func login(_ request: LoginRequest) async throws -> LoginResponse {
        try await send(path: "login", method: "POST", body: request, authed: false) as LoginResponse
    }

    func verifyTwoFactor(_ request: TwoFactorVerifyRequest) async throws -> LoginResponse {
        try await send(path: "two-factor/verify", method: "POST", body: request, authed: false) as LoginResponse
    }

    func resendTwoFactor(_ request: TwoFactorResendRequest) async throws {
        let _: EmptyResponse = try await send(path: "two-factor/resend", method: "POST", body: request, authed: false)
    }

    func logout() async {
        _ = try? await send(path: "logout", method: "POST", body: Optional<String>.none, authed: true) as EmptyResponse
    }

    func profile() async throws -> ProfileResponse {
        try await send(path: "me", method: "GET", body: Optional<String>.none, authed: true) as ProfileResponse
    }

    // MARK: - Data

    func stats() async throws -> StatsResponse {
        try await send(path: "me/stats", method: "GET", body: Optional<String>.none, authed: true) as StatsResponse
    }

    func messages(page: Int = 1, perPage: Int = 50) async throws -> PaginatedMessages {
        let query = "me/messages?page=\(page)&per_page=\(perPage)"
        return try await send(path: query, method: "GET", body: Optional<String>.none, authed: true) as PaginatedMessages
    }

    func message(id: Int) async throws -> MessageDetail {
        try await send(path: "me/messages/\(id)", method: "GET", body: Optional<String>.none, authed: true) as MessageDetail
    }

    func alerts() async throws -> [AlertItem] {
        try await send(path: "me/alerts", method: "GET", body: Optional<String>.none, authed: true) as [AlertItem]
    }

    func markAlertRead(id: Int) async throws {
        let _: AlertItem = try await send(path: "me/alerts/\(id)/read", method: "POST", body: Optional<String>.none, authed: true)
    }

    func markAllAlertsRead() async throws {
        let _: EmptyResponse = try await send(path: "me/alerts/read-all", method: "POST", body: Optional<String>.none, authed: true)
    }

    func registerPushToken(_ token: String) async throws {
        let body = PushTokenRequest(platform: "ios", token: token)
        let _: EmptyResponse = try await send(path: "me/push-tokens", method: "POST", body: body, authed: true)
    }

    func unregisterPushToken(_ token: String) async {
        _ = try? await send(path: "me/push-tokens?token=\(token)", method: "DELETE", body: Optional<String>.none, authed: true) as EmptyResponse
    }

    // MARK: - Core

    private func send<T: Decodable, B: Encodable>(
        path: String,
        method: String,
        body: B?,
        authed: Bool
    ) async throws -> T {
        guard let url = URL(string: path, relativeTo: APIConfig.baseURL) else {
            throw ViewerError.invalidResponse
        }

        var request = URLRequest(url: url)
        request.httpMethod = method
        request.setValue("application/json", forHTTPHeaderField: "Accept")
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")

        if authed, let token = SecureStore.shared.apiToken, !token.isEmpty {
            request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
        }

        if method != "GET", let body {
            request.httpBody = try encoder.encode(body)
        }

        let (data, response) = try await session.data(for: request)
        guard let http = response as? HTTPURLResponse else { throw ViewerError.invalidResponse }

        if http.statusCode == 401 {
            throw ViewerError.unauthorized
        }

        guard (200...299).contains(http.statusCode) else {
            if http.statusCode == 503 {
                let message = (try? decoder.decode(ApiErrorBody.self, from: data))?.message
                    ?? "The service is undergoing scheduled maintenance. Please try again later."
                throw ViewerError.maintenance(message)
            }
            let message = (try? decoder.decode(ApiErrorBody.self, from: data))?.message
                ?? "Request failed (\(http.statusCode))."
            throw ViewerError.server(message)
        }

        // Empty body (e.g. logout / mark read returns 200 with body) — decode safely.
        if data.isEmpty {
            if T.self == EmptyResponse.self { return EmptyResponse() as! T }
            throw ViewerError.invalidResponse
        }
        return try decoder.decode(T.self, from: data)
    }
}

struct EmptyResponse: Decodable {}
