import 'dart:async';

import 'package:cloud_functions/cloud_functions.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:flutter_facebook_auth/flutter_facebook_auth.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:google_sign_in/google_sign_in.dart';
import 'package:kasy_kit/core/data/entities/user_entity.dart';
import 'package:kasy_kit/features/authentication/api/authentication_api_interface.dart';
import 'package:kasy_kit/features/authentication/repositories/exceptions/authentication_exceptions.dart';
import 'package:kasy_kit/google_auth_options.dart';
import 'package:logger/logger.dart';

final authenticationApiProvider = Provider<AuthenticationApi>(
  (ref) => FirebaseAuthenticationApi(
    FirebaseAuth.instance,
    FirebaseFunctions.instanceFor(region: 'europe-west1'),
  ),
);

class FirebaseAuthenticationApi implements AuthenticationApi {
  final FirebaseAuth _auth;

  // ignore: unused_field - this is just to show you how to use Firebase Functions if needed
  final FirebaseFunctions _firebaseFunctions;

  User? _user;

  bool hasInit = false;

  FirebaseAuthenticationApi(this._auth, this._firebaseFunctions);

  @override
  Future<void> init() async {
    _auth.userChanges().listen((event) {
      if (!hasInit) {
        hasInit = true;
      }
      _user = event;
    });
    var attempts = 0;
    while (!hasInit && attempts < 20) {
      await Future.delayed(const Duration(milliseconds: 500));
      attempts++;
    }
    if (!hasInit) throw Exception('Firebase Auth init timeout after 10s');
  }

  Stream<Credentials?> onAuthStateChange() {
    return _auth.authStateChanges().map((user) {
      if (user == null) {
        return null;
      }
      return Credentials(id: user.uid, token: "");
    });
  }

  @override
  Future<Credentials?> get() async {
    if (_user != null) {
      return Future.value(Credentials(id: _user!.uid, token: ""));
    }
    return null;
  }

  @override
  Future<Credentials> signinAnonymously() {
    return _auth.signInAnonymously().then(
      (value) => Credentials(
        id: value.user!.uid,
        token: value.credential?.token.toString() ?? '',
      ),
      onError: (error) {
        Logger().e("Error while signing in anonymously: $error");
        Logger().e(
          "Please check you enabled anonymous sign-in in Firebase: https://firebase.google.com/docs/auth/flutter/anonymous-auth",
        );
        throw Exception(error.toString());
      },
    );
  }

  @override
  Future<Credentials> signin(String email, String password) {
    return _auth
        .signInWithEmailAndPassword(email: email, password: password)
        .then(
          (value) => Credentials(
            id: value.user!.uid,
            token: value.credential?.token.toString() ?? '',
          ),
        );
  }

  @override
  Future<void> recoverPassword(String email) async {
    await _auth.sendPasswordResetEmail(email: email);
  }

  @override
  Future<void> signout() {
    _user = null;
    return _auth.signOut();
  }

  @override
  Future<Credentials> signup(String email, String password) async {
    if (_auth.currentUser == null) {
      return _auth
          .createUserWithEmailAndPassword(email: email, password: password)
          .then(
            (value) => Credentials(
              id: value.user!.uid,
              token: value.credential?.token.toString() ?? '',
            ),
          );
    }
    final credential = EmailAuthProvider.credential(email: email, password: password);
    try {
      final resultUser = await _auth.currentUser!.linkWithCredential(credential);
      return Credentials(
        id: resultUser.user!.uid,
        token: resultUser.credential?.token.toString() ?? '',
      );
    } on FirebaseAuthException catch (e) {
      if (e.code == 'email-already-in-use' || e.code == 'credential-already-in-use') {
        // Delete orphaned anonymous user before signing in with existing account.
        final anonymousUser = _auth.currentUser;
        if (anonymousUser != null && anonymousUser.isAnonymous) {
          await anonymousUser.delete();
        }
        final result = await _auth.signInWithEmailAndPassword(email: email, password: password);
        return Credentials(id: result.user!.uid, token: result.credential?.token.toString() ?? '');
      }
      rethrow;
    }
  }

  @override
  Future<Credentials> signinWithGoogle() async {
    if (kIsWeb) {
      final googleProvider = GoogleAuthProvider();
      googleProvider.addScope(
        'https://www.googleapis.com/auth/contacts.readonly',
      );
      // Optionally you can add custom parameters to the provider
      // googleProvider.setCustomParameters({
      //   'login_hint': 'user@example.com'
      // });
      try {
        final credentials = await _auth.signInWithPopup(googleProvider);
        return Credentials(
          id: credentials.user!.uid,
          token: credentials.credential?.token.toString() ?? '',
        );
      } on FirebaseAuthException catch (e) {
        if (e.code == 'popup-closed-by-user' || e.code == 'cancelled-popup-request') {
          throw const UserCancelledSignInException();
        }
        rethrow;
      }
    }
    try {
      await GoogleSignIn.instance.initialize(serverClientId: kGoogleWebClientId);
      final googleUser = await GoogleSignIn.instance.authenticate();
      final googleAuth = googleUser.authentication;
      final credential = GoogleAuthProvider.credential(idToken: googleAuth.idToken);
      return _auth
          .signInWithCredential(credential)
          .then((value) => Credentials(id: value.user!.uid, token: ''));
    } on GoogleSignInException catch (e) {
      if (e.code == GoogleSignInExceptionCode.canceled) throw const UserCancelledSignInException();
      rethrow;
    } on PlatformException catch (e) {
      if (e.code == 'sign_in_canceled' || e.code == 'sign_in_cancelled') {
        throw const UserCancelledSignInException();
      }
      rethrow;
    }
  }

  /// Sign up with Google and link the account to the current anonymous user.
  /// Uses GoogleSignIn (native account picker) so the user sees the system
  /// account chooser dialog instead of a browser tab.
  /// The anonymous UID is preserved — user data is not lost.
  /// On web, uses Firebase signInWithPopup (GoogleSignIn native SDK is not available).
  /// Falls back to regular sign-in if the Google account already exists.
  @override
  Future<Credentials> signupFromAnonymousWithGoogle() async {
    if (kIsWeb) {
      final googleProvider = GoogleAuthProvider();
      try {
        final userCredential = _auth.currentUser != null
            ? await _auth.currentUser!.linkWithPopup(googleProvider)
            : await _auth.signInWithPopup(googleProvider);
        return Credentials(
          id: userCredential.user!.uid,
          token: userCredential.credential?.token.toString() ?? '',
        );
      } on FirebaseAuthException catch (e) {
        if (e.code == 'popup-closed-by-user' || e.code == 'cancelled-popup-request') {
          throw const UserCancelledSignInException();
        }
        if (e.code == 'credential-already-in-use') {
          // Delete orphaned anonymous user before signing in with existing account.
          final anonymousUser = _auth.currentUser;
          if (anonymousUser != null && anonymousUser.isAnonymous) {
            await anonymousUser.delete();
          }
          final userCredential = await _auth.signInWithPopup(googleProvider);
          return Credentials(id: userCredential.user!.uid, token: userCredential.credential?.token.toString() ?? '');
        }
        rethrow;
      }
    }
    late final GoogleSignInAccount googleUser;
    try {
      await GoogleSignIn.instance.initialize(serverClientId: kGoogleWebClientId);
      googleUser = await GoogleSignIn.instance.authenticate();
    } on GoogleSignInException catch (e) {
      if (e.code == GoogleSignInExceptionCode.canceled) throw const UserCancelledSignInException();
      rethrow;
    } on PlatformException catch (e) {
      if (e.code == 'sign_in_canceled' || e.code == 'sign_in_cancelled') {
        throw const UserCancelledSignInException();
      }
      rethrow;
    }
    final googleAuth = googleUser.authentication;
    final credential = GoogleAuthProvider.credential(idToken: googleAuth.idToken);
    final currentUser = _auth.currentUser;
    if (currentUser == null) {
      // No anonymous session — fall back to regular sign-in.
      try {
        final result = await _auth.signInWithCredential(credential);
        return Credentials(id: result.user!.uid, token: result.credential?.token.toString() ?? '');
      } on FirebaseAuthException catch (e) {
        if (e.code == 'canceled' || e.code == 'popup-closed-by-user' || e.code == 'cancelled-popup-request') {
          throw const UserCancelledSignInException();
        }
        rethrow;
      }
    }
    try {
      final result = await currentUser.linkWithCredential(credential);
      if (result.user != null && (result.user!.displayName?.isEmpty ?? true)) {
        final name = googleUser.displayName;
        if (name != null && name.isNotEmpty) {
          await result.user!.updateDisplayName(name);
        }
      }
      return Credentials(id: result.user!.uid, token: result.credential?.token.toString() ?? '');
    } on FirebaseAuthException catch (e) {
      if (e.code == 'credential-already-in-use') {
        // Google account already exists — delete orphaned anonymous user then sign in.
        final anonymousUser = _auth.currentUser;
        if (anonymousUser != null && anonymousUser.isAnonymous) {
          await anonymousUser.delete();
        }
        final result = await _auth.signInWithCredential(credential);
        return Credentials(id: result.user!.uid, token: result.credential?.token.toString() ?? '');
      }
      rethrow;
    }
  }

  /// Sign-in with Google Play Games.
  /// Note: google_sign_in 7.x removed SignInOption.games. This uses regular
  /// Google Sign-In. For full Play Games support, use firebase_auth_games_services.
  @override
  Future<Credentials> signinWithGooglePlay() {
    return signinWithGoogle();
  }

  @override
  Future<Credentials> signinWithApple() async {
    final appleProvider = AppleAuthProvider();
    appleProvider.addScope('email');
    try {
      final value = kIsWeb
          ? await _auth.signInWithPopup(appleProvider)
          : await _auth.signInWithProvider(appleProvider);
      return Credentials(
        id: value.user!.uid,
        token: value.credential?.token.toString() ?? '',
      );
    } on FirebaseAuthException catch (e) {
      if (e.code == 'canceled' || e.code == 'popup-closed-by-user' || e.code == 'cancelled-popup-request') {
        throw const UserCancelledSignInException();
      }
      rethrow;
    }
  }

  /// Sign up with Apple and link the account to the current anonymous user.
  /// Falls back to regular sign-in if the Apple account already exists.
  @override
  Future<Credentials> signupFromAnonymousWithApple() async {
    final appleProvider = AppleAuthProvider();
    appleProvider.addScope('email');
    final currentUser = _auth.currentUser;
    if (currentUser == null) {
      // No anonymous session — fall back to regular sign-in.
      try {
        final result = kIsWeb
            ? await _auth.signInWithPopup(appleProvider)
            : await _auth.signInWithProvider(appleProvider);
        return Credentials(id: result.user!.uid, token: result.credential?.token.toString() ?? '');
      } on FirebaseAuthException catch (e) {
        if (e.code == 'canceled' || e.code == 'popup-closed-by-user' || e.code == 'cancelled-popup-request') {
          throw const UserCancelledSignInException();
        }
        rethrow;
      }
    }
    try {
      final result = await currentUser.linkWithProvider(appleProvider);
      return Credentials(id: result.user!.uid, token: result.credential?.token.toString() ?? '');
    } on FirebaseAuthException catch (e) {
      if (e.code == 'canceled' || e.code == 'popup-closed-by-user') {
        throw const UserCancelledSignInException();
      }
      if (e.code == 'credential-already-in-use') {
        // Delete orphaned anonymous user before signing in with existing account.
        final anonymousUser = _auth.currentUser;
        if (anonymousUser != null && anonymousUser.isAnonymous) {
          await anonymousUser.delete();
        }
        // Use the credential from the exception to avoid showing Apple popup again.
        final cred = e.credential;
        if (cred != null) {
          final result = await _auth.signInWithCredential(cred);
          return Credentials(id: result.user!.uid, token: result.credential?.token.toString() ?? '');
        }
        final result = kIsWeb
            ? await _auth.signInWithPopup(appleProvider)
            : await _auth.signInWithProvider(appleProvider);
        return Credentials(id: result.user!.uid, token: result.credential?.token.toString() ?? '');
      }
      rethrow;
    }
  }

  /// Sign up with Facebook and link the account to the current anonymous user.
  /// Falls back to regular sign-in if the Facebook account already exists.
  @override
  Future<Credentials> signupFromAnonymousWithFacebook() async {
    final loginResult = await FacebookAuth.instance.login();
    if (loginResult.status == LoginStatus.cancelled) throw const UserCancelledSignInException();
    if (loginResult.status != LoginStatus.success || loginResult.accessToken == null) {
      throw Exception('Facebook login failed: ${loginResult.message}');
    }
    final credential = FacebookAuthProvider.credential(loginResult.accessToken!.tokenString);
    final currentUser = _auth.currentUser;
    if (currentUser == null) {
      try {
        final result = await _auth.signInWithCredential(credential);
        return Credentials(id: result.user!.uid, token: result.credential?.token.toString() ?? '');
      } on FirebaseAuthException catch (e) {
        if (e.code == 'canceled' || e.code == 'popup-closed-by-user' || e.code == 'cancelled-popup-request') {
          throw const UserCancelledSignInException();
        }
        rethrow;
      }
    }
    try {
      final result = await currentUser.linkWithCredential(credential);
      return Credentials(id: result.user!.uid, token: result.credential?.token.toString() ?? '');
    } on FirebaseAuthException catch (e) {
      if (e.code == 'credential-already-in-use') {
        // Delete orphaned anonymous user before signing in with existing account.
        final anonymousUser = _auth.currentUser;
        if (anonymousUser != null && anonymousUser.isAnonymous) {
          await anonymousUser.delete();
        }
        final result = await _auth.signInWithCredential(credential);
        return Credentials(id: result.user!.uid, token: result.credential?.token.toString() ?? '');
      }
      rethrow;
    }
  }

  @override
  Future<Credentials> signinWithFacebook() async {
    final LoginResult loginResult = await FacebookAuth.instance.login();
    if (loginResult.status == LoginStatus.cancelled) throw const UserCancelledSignInException();
    if (loginResult.status != LoginStatus.success || loginResult.accessToken == null) {
      throw Exception('Facebook login failed: ${loginResult.message}');
    }
    final credential = FacebookAuthProvider.credential(loginResult.accessToken!.tokenString);
    return _auth
        .signInWithCredential(credential)
        .then(
          (value) => Credentials(
            id: value.user!.uid,
            token: value.credential?.token.toString() ?? '',
          ),
        );
  }

  @override
  Future<String> signinWithPhone(String phoneNumber) async {
    final normalizedPhoneNumber = _normalizePhoneNumber(phoneNumber);
    final completer = Completer<String>();

    await _auth.verifyPhoneNumber(
      phoneNumber: normalizedPhoneNumber,
      verificationCompleted: (credentials) async {
        await _auth.signInWithCredential(credentials);
      },
      verificationFailed: (FirebaseAuthException error) {
        if (error.code == 'invalid-phone-number') {
          completer.completeError(
            PhoneAuthException(code: 0, message: 'Invalid phone number'),
          );
          return;
        }
        completer.completeError(
          PhoneAuthException(code: 0, message: 'Unknown error: $error'),
        );
      },
      codeSent: (String verificationId, int? resendToken) {
        if (!completer.isCompleted) {
          completer.complete(verificationId);
        }
      },
      codeAutoRetrievalTimeout: (String verificationId) {
        if (!completer.isCompleted) {
          completer.completeError(
            PhoneAuthException(code: 0, message: 'Code auto retrieval timeout'),
          );
        }
      },
    );

    return completer.future;
  }

  @override
  Future<Credentials> verifyPhoneAuth(String verificationId, String otp) {
    final phoneProvider = PhoneAuthProvider.credential(
      verificationId: verificationId,
      smsCode: otp,
    );
    return _auth
        .signInWithCredential(phoneProvider)
        .then((value) => Credentials(id: value.user!.uid, token: ''));
  }

  @override
  Future<String> updateAuthPhone(String phoneNumber) async {
    final normalizedPhoneNumber = _normalizePhoneNumber(phoneNumber);
    final completer = Completer<String>();
    await _auth.verifyPhoneNumber(
      phoneNumber: normalizedPhoneNumber,
      verificationCompleted: (credentials) {
        _auth.currentUser!.updatePhoneNumber(
          PhoneAuthProvider.credential(
            verificationId: credentials.verificationId!,
            smsCode: credentials.smsCode!,
          ),
        );
      },
      verificationFailed: (FirebaseAuthException error) {
        if (error.code == 'invalid-phone-number') {
          completer.completeError(
            PhoneAuthException(code: 0, message: 'Invalid phone number'),
          );
          return;
        }
        if (error.code == 'too-many-requests') {
          completer.completeError(PhoneAlreadyLinkedException());
          return;
        }
        completer.completeError(
          PhoneAuthException(code: 0, message: 'Unknown error: $error'),
        );
      },
      codeSent: (String verificationId, int? resendToken) {
        completer.complete(verificationId);
      },
      codeAutoRetrievalTimeout: (String verificationId) {
        if (!completer.isCompleted) {
          completer.completeError(
            PhoneAuthException(code: 0, message: 'Code auto retrieval timeout'),
          );
        }
      },
    );
    return completer.future;
  }

  @override
  Future<Credentials> confirmLinkPhoneAuth(String verificationId, String otp) {
    return _auth.currentUser!
        .updatePhoneNumber(
          PhoneAuthProvider.credential(
            verificationId: verificationId,
            smsCode: otp,
          ),
        )
        .catchError((error) {
          if (error is FirebaseAuthException &&
              error.code == 'credential-already-in-use') {
            throw PhoneAlreadyLinkedException();
          }
          throw "failed_to_confirm_link_phone_auth";
        })
        .then((value) => Credentials(id: _user!.uid, token: ''));
  }

  @override
  Future<String?> getCurrentUserEmail() async => _auth.currentUser?.email;

  @override
  Future<String?> getCurrentUserDisplayName() async =>
      _auth.currentUser?.displayName;

  String _normalizePhoneNumber(String phoneNumber) {
    String normalized = phoneNumber.replaceAll(RegExp(r'\D'), '');
    if (!normalized.startsWith('+')) {
      normalized = '+$normalized';
    }
    return normalized;
  }
}
