import Foundation

/// Response-Modelle für /api/magic/* Endpoints
struct MagicRegistration: Codable {
    let deviceId: String
    let dnsToken: String
    let profileUrl: String
    let existing: Bool
}

enum MagicDeviceSource: String, Codable {
    case magic
    case locked
    case protected
}

struct MagicDevice: Codable, Identifiable {
    let source: MagicDeviceSource?
    let deviceId: String
    let hostname: String
    let model: String?
    let osVersion: String?
    let magicEnrolledAt: String
    let releaseRequestedAt: String?
    let releaseAvailableAt: String?

    var id: String { "\(source?.rawValue ?? "magic"):\(deviceId)" }

    /// Default zu `.magic` falls Backend (alte Version) das Feld nicht setzt.
    var resolvedSource: MagicDeviceSource { source ?? .magic }
    
    var enrolledDate: Date? {
        parseISO(magicEnrolledAt)
    }
    
    var releaseDate: Date? {
        guard let iso = releaseAvailableAt else { return nil }
        return parseISO(iso)
    }
    
    var isReleasing: Bool {
        releaseRequestedAt != nil
    }
}

struct MagicReleaseResponse: Codable {
    let releaseRequestedAt: String
    let releaseAvailableAt: String
    
    var releaseDate: Date? {
        parseISO(releaseAvailableAt)
    }
}

/// Parses ISO8601 mit + ohne fractional seconds (Backend sendet `.000Z`-Suffix).
private func parseISO(_ s: String) -> Date? {
    let f1 = ISO8601DateFormatter()
    f1.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
    if let d = f1.date(from: s) { return d }
    let f2 = ISO8601DateFormatter()
    f2.formatOptions = [.withInternetDateTime]
    return f2.date(from: s)
}

/// User-Profil aus /api/magic/me \u2014 f\u00fcr Hub-Header (Avatar + Nickname).
struct MagicUserProfile: Codable {
    let nickname: String?
    let avatar: String?
    let plan: String?
}

enum MagicError: Error, LocalizedError {
    case unauthorized
    case limitReached(activeBindings: [MagicDevice])
    case networkError(String)
    case httpError(Int, String)
    case configMissing(String)
    case decodingError(String)
    
    var errorDescription: String? {
        switch self {
        case .unauthorized:
            return "Nicht authentifiziert. Bitte neu einloggen."
        case .limitReached(let bindings):
            return "Device-Limit erreicht (\(bindings.count) Geräte). Bitte zuerst ein Gerät freigeben."
        case .networkError(let msg):
            return "Netzwerkfehler: \(msg)"
        case .httpError(let status, let msg):
            return "HTTP \(status): \(msg)"
        case .configMissing(let msg):
            return "Config fehlt: \(msg)"
        case .decodingError(let msg):
            return "Response-Parse-Fehler: \(msg)"
        }
    }
}

/// HTTP-Client für ReBreak Magic Backend API (/api/magic/*).
/// Injiziert automatisch JWT-Auth via AuthService.
@MainActor
final class MagicAPIClient {
    static let shared = MagicAPIClient()

    // Default-Backend — zentral damit der Wert nur einmal im Binary steht.
    private static let defaultBackendURL = "https://staging.rebreak.org"

    private let authService = AuthService.shared

    private init() {}

    // MARK: - Config

    private struct Config: Codable {
        let backendBaseUrl: String?
    }

    private static let configPath: String = {
        let home = FileManager.default.homeDirectoryForCurrentUser.path
        return "\(home)/.config/rebreak-magic/config.json"
    }()

    private var baseURL: String {
        get throws {
            // Override via env var for testing
            if let envUrl = ProcessInfo.processInfo.environment["REBREAK_BACKEND_URL"] {
                return envUrl
            }

            let url = URL(fileURLWithPath: Self.configPath)
            guard FileManager.default.fileExists(atPath: Self.configPath) else {
                return Self.defaultBackendURL
            }

            do {
                let data = try Data(contentsOf: url)
                let config = try JSONDecoder().decode(Config.self, from: data)
                return config.backendBaseUrl ?? Self.defaultBackendURL
            } catch {
                return Self.defaultBackendURL
            }
        }
    }
    
    // MARK: - Register Device
    
    func register(deviceId: String, hostname: String, model: String, osVersion: String) async throws -> MagicRegistration {
        let session = try await authService.refreshSessionIfNeeded()
        let url = try URL(string: "\(baseURL)/api/magic/register")!
        
        var request = URLRequest(url: url)
        request.httpMethod = "POST"
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        request.setValue("Bearer \(session.accessToken)", forHTTPHeaderField: "Authorization")
        
        let body: [String: String] = [
            "deviceId": deviceId,
            "hostname": hostname,
            "model": model,
            "osVersion": osVersion
        ]
        request.httpBody = try JSONEncoder().encode(body)
        
        let (data, response) = try await URLSession.shared.data(for: request)
        
        guard let httpResponse = response as? HTTPURLResponse else {
            throw MagicError.networkError("Keine HTTP-Response")
        }
        
        if httpResponse.statusCode == 401 {
            await authService.signOut()
            throw MagicError.unauthorized
        }
        
        if httpResponse.statusCode == 409 {
            // Limit reached
            struct LimitError: Codable {
                let code: String
                let activeBindings: [MagicDevice]
            }
            struct ErrorResponse: Codable {
                let data: LimitError
            }
            if let errorData = try? JSONDecoder().decode(ErrorResponse.self, from: data) {
                throw MagicError.limitReached(activeBindings: errorData.data.activeBindings)
            }
            throw MagicError.httpError(409, "Device-Limit erreicht")
        }
        
        guard httpResponse.statusCode == 200 else {
            let body = String(data: data, encoding: .utf8) ?? ""
            throw MagicError.httpError(httpResponse.statusCode, body)
        }
        
        struct Response: Codable {
            let success: Bool
            let data: MagicRegistration
        }
        
        do {
            let response = try JSONDecoder().decode(Response.self, from: data)
            return response.data
        } catch {
            throw MagicError.decodingError(error.localizedDescription)
        }
    }
    
    // MARK: - List Devices
    
    func listDevices() async throws -> [MagicDevice] {
        let session = try await authService.refreshSessionIfNeeded()
        let url = try URL(string: "\(baseURL)/api/magic/devices")!
        
        var request = URLRequest(url: url)
        request.httpMethod = "GET"
        request.setValue("Bearer \(session.accessToken)", forHTTPHeaderField: "Authorization")
        
        let (data, response) = try await URLSession.shared.data(for: request)
        
        guard let httpResponse = response as? HTTPURLResponse else {
            throw MagicError.networkError("Keine HTTP-Response")
        }
        
        if httpResponse.statusCode == 401 {
            await authService.signOut()
            throw MagicError.unauthorized
        }
        
        guard httpResponse.statusCode == 200 else {
            let body = String(data: data, encoding: .utf8) ?? ""
            throw MagicError.httpError(httpResponse.statusCode, body)
        }
        
        struct Response: Codable {
            let success: Bool
            let data: [MagicDevice]
        }
        
        do {
            let response = try JSONDecoder().decode(Response.self, from: data)
            return response.data
        } catch {
            throw MagicError.decodingError(error.localizedDescription)
        }
    }
    
    // MARK: - Request Release
    
    func requestRelease(deviceId: String) async throws -> Date {
        let session = try await authService.refreshSessionIfNeeded()
        let url = try URL(string: "\(baseURL)/api/magic/devices/\(deviceId)/request-release")!
        
        var request = URLRequest(url: url)
        request.httpMethod = "POST"
        request.setValue("Bearer \(session.accessToken)", forHTTPHeaderField: "Authorization")
        
        let (data, response) = try await URLSession.shared.data(for: request)
        
        guard let httpResponse = response as? HTTPURLResponse else {
            throw MagicError.networkError("Keine HTTP-Response")
        }
        
        if httpResponse.statusCode == 401 {
            await authService.signOut()
            throw MagicError.unauthorized
        }
        
        guard httpResponse.statusCode == 200 else {
            let body = String(data: data, encoding: .utf8) ?? ""
            throw MagicError.httpError(httpResponse.statusCode, body)
        }
        
        struct Response: Codable {
            let success: Bool
            let data: MagicReleaseResponse
        }
        
        do {
            let response = try JSONDecoder().decode(Response.self, from: data)
            guard let date = response.data.releaseDate else {
                throw MagicError.decodingError("releaseAvailableAt parse failed")
            }
            return date
        } catch {
            throw MagicError.decodingError(error.localizedDescription)
        }
    }
    
    // MARK: - Cancel Release
    
    func cancelRelease(deviceId: String) async throws {
        let session = try await authService.refreshSessionIfNeeded()
        let url = try URL(string: "\(baseURL)/api/magic/devices/\(deviceId)/cancel-release")!
        
        var request = URLRequest(url: url)
        request.httpMethod = "POST"
        request.setValue("Bearer \(session.accessToken)", forHTTPHeaderField: "Authorization")
        
        let (data, response) = try await URLSession.shared.data(for: request)
        
        guard let httpResponse = response as? HTTPURLResponse else {
            throw MagicError.networkError("Keine HTTP-Response")
        }
        
        if httpResponse.statusCode == 401 {
            await authService.signOut()
            throw MagicError.unauthorized
        }
        
        guard httpResponse.statusCode == 200 else {
            let body = String(data: data, encoding: .utf8) ?? ""
            throw MagicError.httpError(httpResponse.statusCode, body)
        }
    }
    
    // MARK: - User Profile (Hub-Header)

    func fetchMe() async throws -> MagicUserProfile {
        let session = try await authService.refreshSessionIfNeeded()
        let url = try URL(string: "\(baseURL)/api/magic/me")!

        var request = URLRequest(url: url)
        request.httpMethod = "GET"
        request.setValue("Bearer \(session.accessToken)", forHTTPHeaderField: "Authorization")

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

        guard let httpResponse = response as? HTTPURLResponse else {
            throw MagicError.networkError("Keine HTTP-Response")
        }
        if httpResponse.statusCode == 401 {
            await authService.signOut()
            throw MagicError.unauthorized
        }
        guard httpResponse.statusCode == 200 else {
            let body = String(data: data, encoding: .utf8) ?? ""
            throw MagicError.httpError(httpResponse.statusCode, body)
        }

        struct Response: Codable {
            let success: Bool
            let data: MagicUserProfile
        }
        do {
            return try JSONDecoder().decode(Response.self, from: data).data
        } catch {
            throw MagicError.decodingError(error.localizedDescription)
        }
    }

    // MARK: - Status (token-based, no auth)

    /// Pollt `/api/magic/status?token=` — bestätigt serverseitig, dass das
    /// Binding aktiv ist (`active=true`). Kein JWT nötig, der Token ist das Secret.
    func status(token: String) async throws -> Bool {
        let url = try URL(string: "\(baseURL)/api/magic/status?token=\(token)")!

        var request = URLRequest(url: url)
        request.httpMethod = "GET"
        // KEIN JWT — Token in Query

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

        guard let httpResponse = response as? HTTPURLResponse else {
            throw MagicError.networkError("Keine HTTP-Response")
        }
        guard httpResponse.statusCode == 200 else {
            let body = String(data: data, encoding: .utf8) ?? ""
            throw MagicError.httpError(httpResponse.statusCode, body)
        }

        struct StatusData: Codable { let active: Bool }
        struct Response: Codable {
            let success: Bool
            let data: StatusData
        }
        do {
            return try JSONDecoder().decode(Response.self, from: data).data.active
        } catch {
            throw MagicError.decodingError(error.localizedDescription)
        }
    }

    // MARK: - Download Profile

    func downloadProfile(token: String) async throws -> URL {
        let url = try URL(string: "\(baseURL)/api/magic/profile.mobileconfig?token=\(token)")!
        
        var request = URLRequest(url: url)
        request.httpMethod = "GET"
        // KEIN JWT — Token in Query
        
        let (data, response) = try await URLSession.shared.data(for: request)
        
        guard let httpResponse = response as? HTTPURLResponse else {
            throw MagicError.networkError("Keine HTTP-Response")
        }
        
        guard httpResponse.statusCode == 200 else {
            let body = String(data: data, encoding: .utf8) ?? ""
            throw MagicError.httpError(httpResponse.statusCode, body)
        }
        
        // Save to tmp
        let tmpDir = FileManager.default.temporaryDirectory
        let profilePath = tmpDir.appendingPathComponent("RebreakMagic-\(UUID().uuidString).mobileconfig")
        
        try data.write(to: profilePath)
        
        return profilePath
    }
}
