import Foundation
import Combine

/// Single source of truth for "who is signed in" — every feature screen reads
/// `AppState.shared` (injected via `.environmentObject`) rather than talking
/// to `AuthAPI`/`KeychainTokenStore` directly, so there is exactly one place
/// that decides what "signed in" means and exactly one place a 401 anywhere
/// in the app routes back to the login screen.
@MainActor
final class AppState: ObservableObject {
    enum AuthPhase: Equatable {
        /// Checking the Keychain / pinging `/api/auth/session` on launch.
        case checking
        case signedOut
        case signedIn(User)
    }

    @Published private(set) var phase: AuthPhase = .checking
    @Published var lastError: String?

    private var cancellables = Set<AnyCancellable>()

    init() {
        NotificationCenter.default.publisher(for: .sessionExpired)
            .sink { [weak self] _ in
                Task { @MainActor in self?.phase = .signedOut }
            }
            .store(in: &cancellables)
    }

    var currentUser: User? {
        if case .signedIn(let user) = phase { return user }
        return nil
    }

    /// Call once on app launch. If a token is already in the Keychain, this
    /// validates it against the server (a locally-stored token could have
    /// been revoked server-side, e.g. via "sign out everywhere") rather than
    /// trusting its mere presence.
    func bootstrap() async {
        guard KeychainTokenStore.shared.token != nil else {
            phase = .signedOut
            return
        }
        do {
            let response = try await AuthAPI.session()
            phase = response.user.map(AuthPhase.signedIn) ?? .signedOut
        } catch {
            phase = .signedOut
        }
    }

    func login(email: String, password: String) async {
        lastError = nil
        do {
            let response = try await AuthAPI.login(email: email, password: password)
            guard let token = response.token else {
                // Should never happen — X-Client: ios always yields a token
                // (see src/app/api/auth/login/route.ts) — but fail safe
                // rather than silently "succeeding" signed out.
                lastError = "Anmeldung fehlgeschlagen. Bitte erneut versuchen."
                return
            }
            KeychainTokenStore.shared.token = token
            // The login response only carries a small subset of the user
            // (see LoginUser) — immediately follow up with /session so the
            // rest of the app has the full profile (isPro, scan quota, ...).
            await bootstrap()
        } catch {
            lastError = error.localizedDescription
        }
    }

    /// Mirrors `login(email:password:)` exactly, just against
    /// `/api/auth/apple` instead — same token-storage/bootstrap follow-up,
    /// same fail-safe if a token somehow doesn't come back. `fullName` is
    /// only ever non-nil the first time this account authorizes the app
    /// (see `AuthAPI.appleSignIn`).
    func signInWithApple(identityToken: String, fullName: String?) async {
        lastError = nil
        do {
            let response = try await AuthAPI.appleSignIn(identityToken: identityToken, fullName: fullName)
            guard let token = response.token else {
                lastError = "Anmeldung mit Apple fehlgeschlagen. Bitte erneut versuchen."
                return
            }
            KeychainTokenStore.shared.token = token
            await bootstrap()
        } catch {
            lastError = error.localizedDescription
        }
    }

    /// Returns true when a confirmation mail was sent (the account still
    /// needs email verification before it can log in) — matches the web
    /// signup flow exactly, including its neutral response (see AuthAPI).
    func signup(name: String?, email: String, password: String) async -> Bool {
        lastError = nil
        do {
            _ = try await AuthAPI.signup(name: name, email: email, password: password)
            return true
        } catch {
            lastError = error.localizedDescription
            return false
        }
    }

    func logout() async {
        try? await AuthAPI.logout()
        KeychainTokenStore.shared.token = nil
        phase = .signedOut
    }
}
