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
}

struct DeviceCredentials {
    let deviceUid: String?
    let apiToken: String?
    let signingSecret: String?
}

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
        session = URLSession(configuration: config)
    }

    private var credentials: DeviceCredentials {
        let store = SecureStore.shared
        return DeviceCredentials(
            deviceUid: store.deviceUid,
            apiToken: store.apiToken,
            signingSecret: store.signingSecret
        )
    }

    func register(company: String, code: String, deviceName: String) async throws -> RegisterResponse {
        try await send(
            path: "device/register",
            method: "POST",
            body: RegisterRequest(company: company, code: code.uppercased(), device_name: deviceName),
            signed: false
        )
    }

    func heartbeat(_ payload: HeartbeatRequest = HeartbeatRequest(
        battery_level: nil,
        sim_slot: nil,
        network_type: nil,
        last_error: nil
    )) async throws -> HeartbeatResponse {
        try await send(path: "device/heartbeat", method: "POST", body: payload, signed: true)
    }

    func sendTestMessage(deviceUid: String) async throws {
        let payload = MessagePayload(
            device_id: deviceUid,
            sender: "\(SecureStore.shared.displayAppName)-iOS",
            message: "Test sync from iOS at \(ISO8601DateFormatter().string(from: Date()))",
            received_at: ISO8601DateFormatter().string(from: Date()),
            sim_slot: nil
        )
        let _: MessageStoreResponse = try await send(path: "messages", method: "POST", body: payload, signed: true)
    }

    func fetchKeywords() async throws -> [String] {
        let response: KeywordsResponse = try await send(
            path: "keywords",
            method: "GET",
            body: Optional<String>.none,
            signed: true
        )
        return response.keywords
    }

    func fetchSenderFilters() async throws -> SenderFiltersResponse {
        try await send(path: "sender-filters", method: "GET", body: Optional<String>.none, signed: true)
    }

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

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

        let creds = credentials
        if let token = creds.apiToken, !token.isEmpty {
            request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
        }
        if let deviceUid = creds.deviceUid, !deviceUid.isEmpty {
            request.setValue(deviceUid, forHTTPHeaderField: "X-Device-Id")
        }

        // Sign every request that carries a signing secret. For GET requests the
        // body is empty, so the signature is the HMAC of an empty string — this
        // matches the server's VerifyDeviceSignature (hash of request content).
        if signed, let secret = creds.signingSecret, !secret.isEmpty {
            if method != "GET", let body {
                let bodyData = try encoder.encode(body)
                let bodyString = String(data: bodyData, encoding: .utf8) ?? ""
                request.setValue(RequestSigner.sign(body: bodyString, secret: secret), forHTTPHeaderField: "X-NotifyHub-Signature")
                request.httpBody = bodyData
            } else {
                request.setValue(RequestSigner.sign(body: "", secret: secret), forHTTPHeaderField: "X-NotifyHub-Signature")
            }
        } else 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 NotifyHubError.invalidResponse
        }

        guard (200...299).contains(http.statusCode) else {
            if http.statusCode == 503 {
                let message = (try? decoder.decode(APIErrorResponse.self, from: data))?.message
                    ?? "The service is undergoing scheduled maintenance. Please try again later."
                throw NotifyHubError.maintenance(message)
            }
            if let apiError = try? decoder.decode(APIErrorResponse.self, from: data) {
                if http.statusCode == 403,
                   let code = apiError.code,
                   code == "trial_expired" || code == "subscription_required" {
                    let message = apiError.message
                        ?? (code == "trial_expired"
                            ? "Trial ended — upgrade to full access to continue"
                            : "Full access required. Renew your subscription to continue.")
                    throw NotifyHubError.accessDenied(code: code, message: message)
                }
                if let message = apiError.message {
                    throw NotifyHubError.server(message)
                }
            }
            throw NotifyHubError.server("Request failed (\(http.statusCode)).")
        }

        return try decoder.decode(T.self, from: data)
    }
}

struct MessageStoreResponse: Decodable {
    let message: String
    let id: Int?
}

struct APIErrorResponse: Decodable {
    let message: String?
    let code: String?
}
