import 'dart:async';

import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_data_connect/firebase_data_connect.dart';
import 'package:krow_data_connect/krow_data_connect.dart';
import 'package:krow_domain/krow_domain.dart' as domain;
import '../../utils/test_phone_numbers.dart';
import '../../domain/ui_entities/auth_mode.dart';

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

/// Implementation of [AuthRepositoryInterface].
class AuthRepositoryImpl implements AuthRepositoryInterface {
  AuthRepositoryImpl() : _service = DataConnectService.instance;

  final DataConnectService _service;
  Completer<String?>? _pendingVerification;

  @override
  Stream<domain.User?> get currentUser => _service.auth
      .authStateChanges()
      .map((User? firebaseUser) {
        if (firebaseUser == null) {
          return null;
        }

        return domain.User(
          id: firebaseUser.uid,
          email: firebaseUser.email ?? '',
          phone: firebaseUser.phoneNumber,
          role: 'staff',
        );
      });

  /// Signs in with a phone number and returns a verification ID.
  @override
  Future<String?> signInWithPhone({required String phoneNumber}) async {
    final Completer<String?> completer = Completer<String?>();
    _pendingVerification = completer;

    await _service.auth.verifyPhoneNumber(
      phoneNumber: phoneNumber,
      verificationCompleted: (PhoneAuthCredential credential) {
        // Skip auto-verification for test numbers to allow manual code entry
        if (TestPhoneNumbers.isTestNumber(phoneNumber)) {
          return;
        }

        // For real numbers, we can support auto-verification if desired.
        // But since this method returns a verificationId for manual OTP entry,
        // we might not handle direct sign-in here unless the architecture changes.
        // Currently, we just ignore it for the completer flow, 
        // or we could sign in directly if the credential is provided.
      },
      verificationFailed: (FirebaseAuthException e) {
        if (!completer.isCompleted) {
          // Map Firebase network errors to NetworkException
          if (e.code == 'network-request-failed' || 
              e.message?.contains('Unable to resolve host') == true) {
            completer.completeError(
              const domain.NetworkException(technicalMessage: 'Auth network failure'),
            );
          } else {
            completer.completeError(
              domain.SignInFailedException(technicalMessage: 'Firebase ${e.code}: ${e.message}'),
            );
          }
        }
      },
      codeSent: (String verificationId, _) {
        if (!completer.isCompleted) {
          completer.complete(verificationId);
        }
      },
      codeAutoRetrievalTimeout: (String verificationId) {
        if (!completer.isCompleted) {
          completer.complete(verificationId);
        }
      },
    );

    return completer.future;
  }

  @override
  void cancelPendingPhoneVerification() {
    final Completer<String?>? completer = _pendingVerification;
    if (completer != null && !completer.isCompleted) {
      completer.completeError(Exception('Phone verification cancelled.'));
    }
    _pendingVerification = null;
  }

  /// Signs out the current user.
  @override
  Future<void> signOut() {
    StaffSessionStore.instance.clear();
    _service.clearCache();
    return _service.auth.signOut();
  }

  /// Verifies an OTP code and returns the authenticated user.
  @override
  Future<domain.User?> verifyOtp({
    required String verificationId,
    required String smsCode,
    required AuthMode mode,
  }) async {
    final PhoneAuthCredential credential = PhoneAuthProvider.credential(
      verificationId: verificationId,
      smsCode: smsCode,
    );
    final UserCredential userCredential = await _service.run(
      () async {
        try {
          return await _service.auth.signInWithCredential(credential);
        } on FirebaseAuthException catch (e) {
          if (e.code == 'invalid-verification-code') {
            throw const domain.InvalidCredentialsException(
              technicalMessage: 'Invalid OTP code entered.',
            );
          }
          rethrow;
        }
      },
      requiresAuthentication: false,
    );
    final User? firebaseUser = userCredential.user;
    if (firebaseUser == null) {
      throw const domain.SignInFailedException(
        technicalMessage:
            'Phone verification failed, no Firebase user received.',
      );
    }

    final QueryResult<GetUserByIdData, GetUserByIdVariables> response =
        await _service.run(
      () => _service.connector
          .getUserById(
            id: firebaseUser.uid,
          )
          .execute(),
      requiresAuthentication: false,
    );
    final GetUserByIdUser? user = response.data.user;

    GetStaffByUserIdStaffs? staffRecord;

    if (mode == AuthMode.signup) {
      if (user == null) {
        await _service.run(
          () => _service.connector
              .createUser(
                id: firebaseUser.uid,
                role: UserBaseRole.USER,
              )
              .userRole('STAFF')
              .execute(),
          requiresAuthentication: false,
        );
      } else {
        // User exists in PostgreSQL. Check if they have a STAFF profile.
        final QueryResult<GetStaffByUserIdData, GetStaffByUserIdVariables>
            staffResponse = await _service.run(
          () => _service.connector
              .getStaffByUserId(
                userId: firebaseUser.uid,
              )
              .execute(),
          requiresAuthentication: false,
        );

        if (staffResponse.data.staffs.isNotEmpty) {
          // If profile exists, they should use Login mode.
          await _service.auth.signOut();
          throw const domain.AccountExistsException(
            technicalMessage:
                'This user already has a staff profile. Please log in.',
          );
        }

        // If they don't have a staff profile but they exist as BUSINESS,
        // they are allowed to "Sign Up" for Staff.
        // We update their userRole to 'BOTH'.
        if (user.userRole == 'BUSINESS') {
          await _service.run(
            () => _service.connector
                .updateUser(id: firebaseUser.uid)
                .userRole('BOTH')
                .execute(),
            requiresAuthentication: false,
          );
        }
      }
    } else {
      if (user == null) {
        await _service.auth.signOut();
        throw const domain.UserNotFoundException(
          technicalMessage: 'Authenticated user profile not found in database.',
        );
      }
      // Allow STAFF or BOTH roles to log in to the Staff App
      if (user.userRole != 'STAFF' && user.userRole != 'BOTH') {
        await _service.auth.signOut();
        throw const domain.UnauthorizedAppException(
          technicalMessage: 'User is not authorized for this app.',
        );
      }

      final QueryResult<GetStaffByUserIdData, GetStaffByUserIdVariables>
          staffResponse = await _service.run(
        () => _service.connector
            .getStaffByUserId(
              userId: firebaseUser.uid,
            )
            .execute(),
        requiresAuthentication: false,
      );
      if (staffResponse.data.staffs.isEmpty) {
        await _service.auth.signOut();
        throw const domain.UserNotFoundException(
          technicalMessage:
              'Your account is not registered yet. Please register first.',
        );
      }
      staffRecord = staffResponse.data.staffs.first;
    }

    final String email = user?.email ?? '';

    //TO-DO: create(registration) user and staff account
    //TO-DO: save user data locally
    final domain.User domainUser = domain.User(
      id: firebaseUser.uid,
      email: email,
      phone: firebaseUser.phoneNumber,
      role: user?.role.stringValue ?? 'USER',
    );
    final domain.Staff? domainStaff = staffRecord == null
        ? null
        : domain.Staff(
            id: staffRecord.id,
            authProviderId: staffRecord.userId,
            name: staffRecord.fullName,
            email: staffRecord.email ?? '',
            phone: staffRecord.phone,
            status: domain.StaffStatus.completedProfile,
            address: staffRecord.addres,
            avatar: staffRecord.photoUrl,
          );
    StaffSessionStore.instance.setSession(
      StaffSession(
        user: domainUser,
        staff: domainStaff,
        ownerId: staffRecord?.ownerId,
      ),
    );
    return domainUser;
  }
  @override
  Future<domain.User?> restoreSession() async {
    final User? firebaseUser = _service.auth.currentUser;
    if (firebaseUser == null) {
      return null;
    }

    try {
      // 1. Fetch User
      final QueryResult<GetUserByIdData, GetUserByIdVariables> response =
          await _service.run(() => _service.connector
              .getUserById(
                id: firebaseUser.uid,
              )
              .execute());
      final GetUserByIdUser? user = response.data.user;

      if (user == null) {
        return null;
      }

      // 2. Check Role
      if (user.userRole != 'STAFF' && user.userRole != 'BOTH') {
        return null;
      }

      // 3. Fetch Staff Profile
      final QueryResult<GetStaffByUserIdData, GetStaffByUserIdVariables>
          staffResponse = await _service.run(() => _service.connector
              .getStaffByUserId(
                userId: firebaseUser.uid,
              )
              .execute());

      if (staffResponse.data.staffs.isEmpty) {
        return null;
      }

      final GetStaffByUserIdStaffs staffRecord = staffResponse.data.staffs.first;

      // 4. Populate Session
      final domain.User domainUser = domain.User(
        id: firebaseUser.uid,
        email: user.email ?? '',
        phone: firebaseUser.phoneNumber,
        role: user.role.stringValue,
      );

      final domain.Staff domainStaff = domain.Staff(
        id: staffRecord.id,
        authProviderId: staffRecord.userId,
        name: staffRecord.fullName,
        email: staffRecord.email ?? '',
        phone: staffRecord.phone,
        status: domain.StaffStatus.completedProfile,
        address: staffRecord.addres,
        avatar: staffRecord.photoUrl,
      );

      StaffSessionStore.instance.setSession(
        StaffSession(
          user: domainUser,
          staff: domainStaff,
          ownerId: staffRecord.ownerId,
        ),
      );

      return domainUser;
    } catch (e) {
      // If restoration fails (network, etc), we rethrow to let UI handle it.
      rethrow;
    }
  }
}
