import AppKit
import Foundation
import Security

/// Magic-Session — Mac-App speichert nur den mgc_*-Token (kein Supabase-JWT mehr).
/// Token wird beim Pairing in der Native-App generiert und hier 1:1 als
/// `Authorization: Bearer mgc_...` an /api/magic/* gesendet.
struct AuthSession: Codable {
    /// "mgc_<base64url>" — wird als Bearer-Token verwendet.
    let accessToken: String
    /// Server-Session-ID (zur Anzeige + Revoke in Native-App).
    let sessionId: String
    /// User-ID nicht zwingend benötigt — wir holen Devices via Token. Display nur.
    let userId: String
    /// Zeitpunkt des Pairings (für Anzeige).
    let createdAt: Date
    /// Optionales Label (z.B. Mac-Hostname). Display nur.
    let label: String?

    // Backwards-compat mit altem Code (immer false — Magic-Tokens laufen nicht ab):
    var isExpired: Bool { false }
    var refreshToken: String { "" }
    var expiresAt: Date { Date.distantFuture }
    var email: String { label ?? "RebreakMagic" }
}

enum AuthError: Error, LocalizedError {
    case invalidCode
    case codeExpired
    case codeUsed
    case networkError(String)
    case configMissing(String)
    case keychainError(OSStatus)
    case tokenExpired
    case refreshFailed

    var errorDescription: String? {
        switch self {
        case .invalidCode:
            return "Code ungültig. Bitte in der Rebreak-App einen neuen Code generieren."
        case .codeExpired:
            return "Code abgelaufen. Bitte einen neuen Code generieren."
        case .codeUsed:
            return "Code wurde bereits verwendet."
        case .networkError(let msg):
            return "Netzwerkfehler: \(msg)"
        case .configMissing(let msg):
            return "Konfiguration fehlt: \(msg)"
        case .keychainError(let status):
            return "Keychain-Fehler: \(status)"
        case .tokenExpired:
            return "Session abgelaufen. Bitte neu pairen."
        case .refreshFailed:
            return "Session ungültig. Bitte neu pairen."
        }
    }
}

private struct PairRedeemResponseEnvelope: Codable {
    let success: Bool
    let data: PairRedeemResponseData
}

private struct PairRedeemResponseData: Codable {
    let token: String
    let sessionId: String
    let createdAt: String
}

/// AuthService — managt Pairing-Code-Login + Keychain-Persistence.
///
/// Config aus ~/.config/rebreak-magic/config.json (optional):
///   { "backendBaseUrl": "https://<backend-host>" }
@MainActor
final class AuthService {
    static let shared = AuthService()

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

    private let keychainService = "org.rebreak.magic"
    private let keychainAccount = "magic-session"
    private var cachedSession: AuthSession?

    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 func loadBackendUrl() -> String {
        if let envUrl = ProcessInfo.processInfo.environment["REBREAK_BACKEND_URL"] {
            return envUrl
        }
        let url = URL(fileURLWithPath: Self.configPath)
        guard FileManager.default.fileExists(atPath: Self.configPath),
              let data = try? Data(contentsOf: url),
              let config = try? JSONDecoder().decode(Config.self, from: data),
              let base = config.backendBaseUrl else {
            return Self.defaultBackendURL
        }
        return base
    }

    // MARK: - Sign In via Pairing-Code

    /// Tauscht einen 6-stelligen Pairing-Code (aus der Native-App) gegen eine
    /// MagicSession. Speichert Token in macOS-Keychain.
    func signInWithPairingCode(_ code: String) async throws -> AuthSession {
        let trimmed = code.trimmingCharacters(in: .whitespaces)
        guard trimmed.range(of: "^\\d{6}$", options: .regularExpression) != nil else {
            throw AuthError.invalidCode
        }

        let base = loadBackendUrl()
        guard let url = URL(string: "\(base)/api/magic/pair/redeem") else {
            throw AuthError.configMissing("Ungültige backendBaseUrl")
        }

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

        let hostname = Host.current().localizedName ?? ProcessInfo.processInfo.hostName
        let body: [String: String] = ["code": trimmed, "label": hostname]
        request.httpBody = try JSONEncoder().encode(body)

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

        guard let http = response as? HTTPURLResponse else {
            throw AuthError.networkError("Keine HTTP-Response")
        }

        switch http.statusCode {
        case 200: break
        case 400, 404: throw AuthError.invalidCode
        case 410:
            let bodyStr = String(data: data, encoding: .utf8) ?? ""
            if bodyStr.contains("verwendet") {
                throw AuthError.codeUsed
            }
            throw AuthError.codeExpired
        default:
            let bodyStr = String(data: data, encoding: .utf8) ?? ""
            throw AuthError.networkError("HTTP \(http.statusCode): \(bodyStr)")
        }

        let envelope = try JSONDecoder().decode(PairRedeemResponseEnvelope.self, from: data)
        let createdAt = ISO8601DateFormatter().date(from: envelope.data.createdAt) ?? Date()

        let session = AuthSession(
            accessToken: envelope.data.token,
            sessionId: envelope.data.sessionId,
            userId: "",
            createdAt: createdAt,
            label: hostname
        )

        try saveToKeychain(session)
        cachedSession = session
        return session
    }

    // MARK: - Sign Out

    func signOut() async {
        cachedSession = nil
        deleteFromKeychain()
    }

    // MARK: - Current Session

    func currentSession() -> AuthSession? {
        if let cached = cachedSession {
            return cached
        }
        if let loaded = loadFromKeychain() {
            cachedSession = loaded
            return loaded
        }
        return nil
    }

    // MARK: - "Refresh" (no-op für Magic-Tokens)

    /// Magic-Tokens laufen nicht ab — diese Methode existiert für Backwards-Compat
    /// mit MagicAPIClient, der sie vor jedem Request aufruft.
    func refreshSessionIfNeeded() async throws -> AuthSession {
        guard let session = currentSession() else {
            throw AuthError.tokenExpired
        }
        return session
    }

    // MARK: - Keychain

    private func saveToKeychain(_ session: AuthSession) throws {
        let data = try JSONEncoder().encode(session)
        let query: [String: Any] = [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrService as String: keychainService,
            kSecAttrAccount as String: keychainAccount,
            kSecValueData as String: data
        ]
        SecItemDelete(query as CFDictionary)
        let status = SecItemAdd(query as CFDictionary, nil)
        guard status == errSecSuccess else {
            throw AuthError.keychainError(status)
        }
    }

    private func loadFromKeychain() -> AuthSession? {
        let query: [String: Any] = [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrService as String: keychainService,
            kSecReturnData as String: true,
            kSecMatchLimit as String: kSecMatchLimitOne
        ]
        var item: CFTypeRef?
        let status = SecItemCopyMatching(query as CFDictionary, &item)
        guard status == errSecSuccess,
              let data = item as? Data,
              let session = try? JSONDecoder().decode(AuthSession.self, from: data) else {
            return nil
        }
        return session
    }

    private func deleteFromKeychain() {
        let query: [String: Any] = [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrService as String: keychainService,
            kSecAttrAccount as String: keychainAccount
        ]
        SecItemDelete(query as CFDictionary)
    }
}
