import Foundation

/// HTTP client for the Chad Music REST API.
@MainActor
@Observable
final class ChadMusicAPIClient {
    static let shared = ChadMusicAPIClient()

    // MARK: - Configuration

    var serverURL: String {
        get { UserDefaults.standard.string(forKey: "chadMusic.serverURL") ?? "" }
        set { UserDefaults.standard.set(newValue, forKey: "chadMusic.serverURL") }
    }

    var isConfigured: Bool {
        !serverURL.isEmpty && KeychainService.loadAPIKey() != nil
    }

    // MARK: - Private

    @ObservationIgnored private let session: URLSession
    @ObservationIgnored private let decoder: JSONDecoder

    init() {
        let config = URLSessionConfiguration.default
        config.timeoutIntervalForRequest = 15
        config.timeoutIntervalForResource = 60
        self.session = URLSession(configuration: config)
        self.decoder = JSONDecoder()
    }

    // MARK: - API Methods

    func fetchStats() async throws -> ChadStats {
        try await get("api/stats")
    }

    func fetchCategory(_ category: ChadCategoryType) async throws -> [ChadCategory] {
        try await get("api/cat/\(category.rawValue)")
    }

    func fetchAlbums() async throws -> [ChadAlbum] {
        try await get("api/cat/album")
    }

    func fetchAlbumTracks(albumId: String) async throws -> [ChadTrack] {
        try await get("api/album/\(albumId)/tracks")
    }

    func testConnection() async -> Result<ChadStats, ChadMusicError> {
        do {
            let stats = try await fetchStats()
            return .success(stats)
        } catch let error as ChadMusicError {
            return .failure(error)
        } catch {
            return .failure(.networkError(error))
        }
    }

    func streamURL(for trackPath: String) -> URL? {
        let trimmed = serverURL.trimmingCharacters(in: .whitespacesAndNewlines)
        guard !trimmed.isEmpty else { return nil }
        let base = trimmed.hasSuffix("/") ? String(trimmed.dropLast()) : trimmed
        let path = trackPath.hasPrefix("/") ? trackPath : "/" + trackPath
        return URL(string: base + path)
    }

    var authHeaders: [String: String] {
        guard let key = KeychainService.loadAPIKey() else { return [:] }
        return ["Authorization": "Bearer \(key)"]
    }

    // MARK: - Private Helpers

    private var baseURL: URL? {
        let trimmed = serverURL.trimmingCharacters(in: .whitespacesAndNewlines)
        guard !trimmed.isEmpty else { return nil }
        let normalized = trimmed.hasSuffix("/") ? trimmed : trimmed + "/"
        return URL(string: normalized)
    }

    private func get<T: Decodable>(_ path: String) async throws -> T {
        guard let base = baseURL else {
            throw ChadMusicError.notConfigured
        }

        let url = base.appending(path: path)
        var request = URLRequest(url: url)
        request.httpMethod = "GET"

        if let apiKey = KeychainService.loadAPIKey() {
            request.setValue("Bearer \(apiKey)", forHTTPHeaderField: "Authorization")
        }

        let (data, response) = try await session.data(for: request)

        guard let httpResponse = response as? HTTPURLResponse else {
            throw ChadMusicError.invalidResponse
        }

        switch httpResponse.statusCode {
        case 200..<300:
            do {
                return try decoder.decode(T.self, from: data)
            } catch {
                throw ChadMusicError.decodingFailed(error)
            }
        case 401:
            throw ChadMusicError.unauthorized
        case 403:
            throw ChadMusicError.forbidden
        case 404:
            throw ChadMusicError.notFound(path)
        default:
            throw ChadMusicError.httpError(httpResponse.statusCode)
        }
    }
}

// MARK: - Errors

enum ChadMusicError: LocalizedError {
    case notConfigured
    case unauthorized
    case forbidden
    case notFound(String)
    case httpError(Int)
    case invalidResponse
    case decodingFailed(Error)
    case networkError(Error)

    var errorDescription: String? {
        switch self {
        case .notConfigured:
            "Chad Music server not configured. Set the server URL and API key in Settings."
        case .unauthorized:
            "Invalid API key (401 Unauthorized)."
        case .forbidden:
            "Access denied (403 Forbidden)."
        case .notFound(let path):
            "Endpoint not found: \(path)"
        case .httpError(let code):
            "Server returned HTTP \(code)."
        case .invalidResponse:
            "Invalid server response."
        case .decodingFailed(let error):
            "Failed to decode response: \(error.localizedDescription)"
        case .networkError(let error):
            "Network error: \(error.localizedDescription)"
        }
    }
}
