import 'dart:developer' as developer;

import 'package:firebase_auth/firebase_auth.dart' as firebase;
import 'package:firebase_data_connect/firebase_data_connect.dart';
import 'package:krow_data_connect/krow_data_connect.dart' as dc;
import 'package:krow_domain/krow_domain.dart'
    show
        InvalidCredentialsException,
        SignInFailedException,
        SignUpFailedException,
        WeakPasswordException,
        AccountExistsException,
        UserNotFoundException,
        UnauthorizedAppException,
        PasswordMismatchException,
        GoogleOnlyAccountException;
import 'package:krow_domain/krow_domain.dart' as domain;

import '../../domain/repositories/auth_repository_interface.dart';

/// Production-ready implementation of the [AuthRepositoryInterface] for the client app.
///
/// This implementation integrates with Firebase Authentication for user
/// identity management and Krow's Data Connect SDK for storing user profile data.
class AuthRepositoryImpl implements AuthRepositoryInterface {
  final firebase.FirebaseAuth _firebaseAuth;
  final dc.ExampleConnector _dataConnect;

  /// Creates an [AuthRepositoryImpl] with the real dependencies.
  AuthRepositoryImpl({
    required firebase.FirebaseAuth firebaseAuth,
    required dc.ExampleConnector dataConnect,
  })  : _firebaseAuth = firebaseAuth,
        _dataConnect = dataConnect;

  @override
  Future<domain.User> signInWithEmail({
    required String email,
    required String password,
  }) async {
    try {
      final firebase.UserCredential credential = await _firebaseAuth.signInWithEmailAndPassword(
        email: email,
        password: password,
      );

      final firebase.User? firebaseUser = credential.user;
      if (firebaseUser == null) {
        throw const SignInFailedException(
          technicalMessage: 'No Firebase user received after sign-in',
        );
      }

      return _getUserProfile(
        firebaseUserId: firebaseUser.uid,
        fallbackEmail: firebaseUser.email ?? email,
        requireBusinessRole: true,
      );

    } on firebase.FirebaseAuthException catch (e) {
      if (e.code == 'invalid-credential' || e.code == 'wrong-password') {
        throw InvalidCredentialsException(
          technicalMessage: 'Firebase error code: ${e.code}',
        );
      } else {
        throw SignInFailedException(
          technicalMessage: 'Firebase auth error: ${e.message}',
        );
      }
    } on domain.AppException {
      rethrow;
    } catch (e) {
      throw SignInFailedException(
        technicalMessage: 'Unexpected error: $e',
      );
    }
  }

  @override
  Future<domain.User> signUpWithEmail({
    required String companyName,
    required String email,
    required String password,
  }) async {
    firebase.User? firebaseUser;
    String? createdBusinessId;

    try {
      // Step 1: Try to create Firebase Auth user
      final firebase.UserCredential credential = await _firebaseAuth.createUserWithEmailAndPassword(
        email: email,
        password: password,
      );

      firebaseUser = credential.user;
      if (firebaseUser == null) {
        throw const SignUpFailedException(
          technicalMessage: 'Firebase user could not be created',
        );
      }

      // New user created successfully, proceed to create PostgreSQL entities
      return await _createBusinessAndUser(
        firebaseUser: firebaseUser,
        companyName: companyName,
        email: email,
        onBusinessCreated: (String businessId) => createdBusinessId = businessId,
      );

    } on firebase.FirebaseAuthException catch (e) {
      if (e.code == 'weak-password') {
        throw WeakPasswordException(
          technicalMessage: 'Firebase: ${e.message}',
        );
      } else if (e.code == 'email-already-in-use') {
        // Email exists in Firebase Auth - try to sign in and complete registration
        return await _handleExistingFirebaseAccount(
          email: email,
          password: password,
          companyName: companyName,
        );
      } else {
        throw SignUpFailedException(
          technicalMessage: 'Firebase auth error: ${e.message}',
        );
      }
    } on domain.AppException {
      // Rollback for our known exceptions
      await _rollbackSignUp(firebaseUser: firebaseUser, businessId: createdBusinessId);
      rethrow;
    } catch (e) {
      // Rollback: Clean up any partially created resources
      await _rollbackSignUp(firebaseUser: firebaseUser, businessId: createdBusinessId);
      throw SignUpFailedException(
        technicalMessage: 'Unexpected error: $e',
      );
    }
  }

  /// Handles the case where email already exists in Firebase Auth.
  ///
  /// This can happen when:
  /// 1. User signed up with Google in another app sharing the same Firebase project
  /// 2. User already has a KROW account
  ///
  /// The flow:
  /// 1. Try to sign in with provided password
  /// 2. If sign-in succeeds, check if BUSINESS user exists in PostgreSQL
  /// 3. If not, create Business + User (user is new to KROW)
  /// 4. If yes, they already have a KROW account
  Future<domain.User> _handleExistingFirebaseAccount({
    required String email,
    required String password,
    required String companyName,
  }) async {
    developer.log('Email exists in Firebase, attempting sign-in: $email', name: 'AuthRepository');

    try {
      // Try to sign in with the provided password
      final firebase.UserCredential credential = await _firebaseAuth.signInWithEmailAndPassword(
        email: email,
        password: password,
      );

      final firebase.User? firebaseUser = credential.user;
      if (firebaseUser == null) {
        throw const SignUpFailedException(
          technicalMessage: 'Sign-in succeeded but no user returned',
        );
      }

      // Sign-in succeeded! Check if user already has a BUSINESS account in PostgreSQL
      final bool hasBusinessAccount = await _checkBusinessUserExists(firebaseUser.uid);

      if (hasBusinessAccount) {
        // User already has a KROW Client account
        developer.log('User already has BUSINESS account: ${firebaseUser.uid}', name: 'AuthRepository');
        throw AccountExistsException(
          technicalMessage: 'User ${firebaseUser.uid} already has BUSINESS role',
        );
      }

      // User exists in Firebase but not in KROW PostgreSQL - create the entities
      developer.log('Creating BUSINESS account for existing Firebase user: ${firebaseUser.uid}', name: 'AuthRepository');
      return await _createBusinessAndUser(
        firebaseUser: firebaseUser,
        companyName: companyName,
        email: email,
        onBusinessCreated: (_) {}, // No rollback needed for existing Firebase user
      );

    } on firebase.FirebaseAuthException catch (e) {
      // Sign-in failed - check why
      developer.log('Sign-in failed with code: ${e.code}', name: 'AuthRepository');

      if (e.code == 'wrong-password' || e.code == 'invalid-credential') {
        // Password doesn't match - check what providers are available
        return await _handlePasswordMismatch(email);
      } else {
        throw SignUpFailedException(
          technicalMessage: 'Firebase sign-in error: ${e.message}',
        );
      }
    } on domain.AppException {
      rethrow;
    }
  }

  /// Handles the case where the password doesn't match the existing account.
  ///
  /// Note: fetchSignInMethodsForEmail was deprecated by Firebase for security
  /// reasons (email enumeration). We show a combined message that covers both
  /// cases: wrong password OR account uses different sign-in method (Google).
  Future<Never> _handlePasswordMismatch(String email) async {
    // We can't distinguish between "wrong password" and "no password provider"
    // due to Firebase deprecating fetchSignInMethodsForEmail.
    // The PasswordMismatchException message covers both scenarios.
    developer.log('Password mismatch or different provider for: $email', name: 'AuthRepository');
    throw PasswordMismatchException(
      technicalMessage: 'Email $email: password mismatch or different auth provider',
    );
  }

  /// Checks if a user with BUSINESS role exists in PostgreSQL.
  Future<bool> _checkBusinessUserExists(String firebaseUserId) async {
    try {
      final QueryResult<dc.GetUserByIdData, dc.GetUserByIdVariables> response =
          await _dataConnect.getUserById(id: firebaseUserId).execute();
      final dc.GetUserByIdUser? user = response.data?.user;
      return user != null && user.userRole == 'BUSINESS';
    } catch (e) {
      developer.log('Error checking business user: $e', name: 'AuthRepository');
      return false;
    }
  }

  /// Creates Business and User entities in PostgreSQL for a Firebase user.
  Future<domain.User> _createBusinessAndUser({
    required firebase.User firebaseUser,
    required String companyName,
    required String email,
    required void Function(String businessId) onBusinessCreated,
  }) async {
    // Create Business entity in PostgreSQL
    final OperationResult<dc.CreateBusinessData, dc.CreateBusinessVariables> createBusinessResponse =
        await _dataConnect.createBusiness(
          businessName: companyName,
          userId: firebaseUser.uid,
          rateGroup: dc.BusinessRateGroup.STANDARD,
          status: dc.BusinessStatus.PENDING,
        ).execute();

    final dc.CreateBusinessBusinessInsert? businessData = createBusinessResponse.data?.business_insert;
    if (businessData == null) {
      throw const SignUpFailedException(
        technicalMessage: 'Business creation failed in PostgreSQL',
      );
    }
    onBusinessCreated(businessData.id);

    // Create User entity in PostgreSQL
    final OperationResult<dc.CreateUserData, dc.CreateUserVariables> createUserResponse =
        await _dataConnect.createUser(
          id: firebaseUser.uid,
          role: dc.UserBaseRole.USER,
        )
        .email(email)
        .userRole('BUSINESS')
        .execute();

    final dc.CreateUserUserInsert? newUserData = createUserResponse.data?.user_insert;
    if (newUserData == null) {
      throw const SignUpFailedException(
        technicalMessage: 'User profile creation failed in PostgreSQL',
      );
    }

    return _getUserProfile(
      firebaseUserId: firebaseUser.uid,
      fallbackEmail: firebaseUser.email ?? email,
    );
  }

  /// Rollback helper to clean up partially created resources during sign-up.
  Future<void> _rollbackSignUp({
    firebase.User? firebaseUser,
    String? businessId,
  }) async {
    // Delete business first (if created)
    if (businessId != null) {
      try {
        await _dataConnect.deleteBusiness(id: businessId).execute();
      } catch (_) {
        // Log but don't throw - we're already in error recovery
      }
    }
    // Delete Firebase user (if created)
    if (firebaseUser != null) {
      try {
        await firebaseUser.delete();
      } catch (_) {
        // Log but don't throw - we're already in error recovery
      }
    }
  }

  @override
  Future<void> signOut() async {
    try {
      await _firebaseAuth.signOut();
      dc.ClientSessionStore.instance.clear();
    } catch (e) {
      throw Exception('Error signing out: ${e.toString()}');
    }
  }

  @override
  Future<domain.User> signInWithSocial({required String provider}) {
    throw UnimplementedError('Social authentication with $provider is not yet implemented.');
  }

  Future<domain.User> _getUserProfile({
    required String firebaseUserId,
    required String? fallbackEmail,
    bool requireBusinessRole = false,
  }) async {
    final QueryResult<dc.GetUserByIdData, dc.GetUserByIdVariables> response = await _dataConnect.getUserById(id: firebaseUserId).execute();
    final dc.GetUserByIdUser? user = response.data?.user;
    if (user == null) {
      throw UserNotFoundException(
        technicalMessage: 'Firebase UID $firebaseUserId not found in users table',
      );
    }
    if (requireBusinessRole && user.userRole != 'BUSINESS') {
      await _firebaseAuth.signOut();
      dc.ClientSessionStore.instance.clear();
      throw UnauthorizedAppException(
        technicalMessage: 'User role is ${user.userRole}, expected BUSINESS',
      );
    }

    final String? email = user.email ?? fallbackEmail;
    if (email == null || email.isEmpty) {
      throw UserNotFoundException(
        technicalMessage: 'User email missing for UID $firebaseUserId',
      );
    }

    final domain.User domainUser = domain.User(
      id: user.id,
      email: email,
      role: user.role.stringValue,
    );

    final QueryResult<dc.GetBusinessesByUserIdData, dc.GetBusinessesByUserIdVariables> businessResponse = await _dataConnect.getBusinessesByUserId(
      userId: firebaseUserId,
    ).execute();
    final dc.GetBusinessesByUserIdBusinesses? business = businessResponse.data.businesses.isNotEmpty
        ? businessResponse.data.businesses.first
        : null;

    dc.ClientSessionStore.instance.setSession(
      dc.ClientSession(
        user: domainUser,
        userPhotoUrl: user.photoUrl,
        business: business == null
            ? null
            : dc.ClientBusinessSession(
                id: business.id,
                businessName: business.businessName,
                email: business.email,
                city: business.city,
                contactName: business.contactName,
                companyLogoUrl: business.companyLogoUrl,
              ),
      ),
    );

    return domainUser;
  }
}
