import Foundation
import FirebaseAuth
import FirebaseCore
import GoogleSignIn

// MARK: - Auth Service

final class AuthService: NSObject, @unchecked Sendable {
    static let shared = AuthService()
    
    private let auth = Auth.auth()
    private let limiter = AuthRateLimiter.shared
    
    private override init() {
        super.init()
    }
    
    // MARK: - Auth State
    
    var currentUserId: String? { auth.currentUser?.uid }
    var currentUserEmail: String? { auth.currentUser?.email }
    var isSignedIn: Bool { auth.currentUser != nil }
    var isAnonymous: Bool { auth.currentUser?.isAnonymous ?? false }
    var isGoogleAccount: Bool {
        auth.currentUser?.providerData.contains { $0.providerID == GoogleAuthProviderID } ?? false
    }
    
    /// Observe auth state changes as an async sequence
    func authStateStream() -> AsyncStream<AuthState> {
        AsyncStream { continuation in
            let listener = Auth.auth().addStateDidChangeListener { _, user in
                if let user = user {
                    continuation.yield(.authenticated(userId: user.uid, isAnonymous: user.isAnonymous))
                } else {
                    continuation.yield(.unauthenticated)
                }
            }
            continuation.onTermination = { _ in
                Auth.auth().removeStateDidChangeListener(listener)
            }
        }
    }
    
    // MARK: - Auth Operations
    
    func signInAnonymously() async throws -> String {
        _ = limiter.recordFailure(.anonymous)
        guard limiter.timeUntilNextAttempt(.anonymous) == 0 else {
            throw AuthError.throttled(limiter.throttleMessage(.anonymous) ?? "Too many attempts. Please wait.")
        }
        
        let result = try await auth.signInAnonymously()
        limiter.recordSuccess(.anonymous)
        return result.user.uid
    }
    
    func signInWithEmail(_ email: String, password: String) async throws -> String {
        guard limiter.timeUntilNextAttempt(.login) == 0 else {
            throw AuthError.throttled(limiter.throttleMessage(.login) ?? "Too many attempts. Please wait.")
        }
        
        do {
            let result = try await auth.signIn(withEmail: email, password: password)
            limiter.recordSuccess(.login)
            return result.user.uid
        } catch {
            limiter.recordFailure(.login)
            throw AuthError.map(error)
        }
    }
    
    func signUpWithEmail(_ email: String, password: String) async throws -> String {
        guard limiter.timeUntilNextAttempt(.signUp) == 0 else {
            throw AuthError.throttled(limiter.throttleMessage(.signUp) ?? "Too many attempts. Please wait.")
        }
        
        do {
            let result = try await auth.createUser(withEmail: email, password: password)
            limiter.recordSuccess(.signUp)
            return result.user.uid
        } catch {
            limiter.recordFailure(.signUp)
            throw AuthError.map(error)
        }
    }
    
    func sendPasswordResetEmail(_ email: String) async throws {
        guard limiter.timeUntilNextAttempt(.passwordReset) == 0 else {
            throw AuthError.throttled(limiter.throttleMessage(.passwordReset) ?? "Too many attempts. Please wait.")
        }
        
        do {
            try await auth.sendPasswordResetEmail(withEmail: email)
            limiter.recordSuccess(.passwordReset)
        } catch {
            limiter.recordFailure(.passwordReset)
            throw AuthError.map(error)
        }
    }
    
    func signInWithGoogle(idToken: String, accessToken: String) async throws -> GoogleSignInResult {
        let credential = GoogleAuthProvider.credential(withIDToken: idToken, accessToken: accessToken)
        let result = try await auth.signIn(with: credential)
        
        let user = result.user
        return GoogleSignInResult(
            uid: user.uid,
            displayName: user.displayName ?? "",
            photoUrl: user.photoURL?.absoluteString ?? "",
            email: user.email ?? "",
            isAnonymous: user.isAnonymous
        )
    }
    
    func signOut() throws {
        try auth.signOut()
    }
    
    func reauthenticateWithEmail(_ email: String, password: String) async throws {
        guard let currentUser = auth.currentUser else {
            throw AuthError.notSignedIn
        }
        let credential = EmailAuthProvider.credential(withEmail: email, password: password)
        try await currentUser.reauthenticate(with: credential)
    }
    
    func deleteAccount() async throws {
        guard let currentUser = auth.currentUser else {
            throw AuthError.notSignedIn
        }
        try await currentUser.delete()
    }
}

// MARK: - Auth Errors

enum AuthError: LocalizedError {
    case throttled(String)
    case notSignedIn
    case networkError
    case invalidCredential
    case emailAlreadyInUse
    case weakPassword
    case userNotFound
    case unknown(Error)
    
    static func map(_ error: Error) -> AuthError {
        let nsError = error as NSError
        switch nsError.code {
        case AuthErrorCode.networkError.rawValue:
            return .networkError
        case AuthErrorCode.invalidCredential.rawValue:
            return .invalidCredential
        case AuthErrorCode.emailAlreadyInUse.rawValue:
            return .emailAlreadyInUse
        case AuthErrorCode.weakPassword.rawValue:
            return .weakPassword
        case AuthErrorCode.userNotFound.rawValue:
            return .userNotFound
        default:
            return .unknown(error)
        }
    }
    
    var errorDescription: String? {
        switch self {
        case .throttled(let msg): return msg
        case .notSignedIn: return "No user is signed in."
        case .networkError: return "Network error. Please check your connection."
        case .invalidCredential: return "Invalid email or password."
        case .emailAlreadyInUse: return "This email is already registered."
        case .weakPassword: return "Password must be at least 6 characters."
        case .userNotFound: return "No account found with this email."
        case .unknown(let error): return error.localizedDescription
        }
    }
}