import 'dart:async';
import 'dart:convert';

import 'package:flutter_login_facebook/flutter_login_facebook.dart';
import 'package:google_sign_in/google_sign_in.dart';
import 'package:guru_utils/auth/auth_credential_manager.dart';
import 'package:json_annotation/json_annotation.dart';
import 'package:sign_in_with_apple/sign_in_with_apple.dart';

part 'account_credentials.g.dart';

class AccountCredentials {}

@JsonSerializable(explicitToJson: true)
class AppleCredential extends Credential {
  /// An identifier associated with the authenticated user.
  ///
  /// This will always be provided on iOS and macOS systems. On Android, however, this will not be present.
  /// This will stay the same between sign ins, until the user deauthorizes your App.
  ///
  /// Can be `null`
  @JsonKey(name: 'user_identifier')
  final String? userIdentifier;

  /// The users given name, in case it was requested.
  /// You will need to provide the [AppleIDAuthorizationScopes.fullName] scope to the [AppleIDAuthorizationRequest] for requesting this information.
  ///
  /// This information will only be provided on the first authorizations.
  /// Upon further authorizations, you will only get the [userIdentifier],
  /// meaning you will need to store this data securely on your servers.
  /// For more information see: https://forums.developer.apple.com/thread/121496
  ///
  /// Can be `null`
  @JsonKey(name: 'given_name')
  final String? givenName;

  /// The users family name, in case it was requested.
  /// You will need to provide the [AppleIDAuthorizationScopes.fullName] scope to the [AppleIDAuthorizationRequest] for requesting this information.
  ///
  /// This information will only be provided on the first authorizations.
  /// Upon further authorizations, you will only get the [userIdentifier],
  /// meaning you will need to store this data securely on your servers.
  /// For more information see: https://forums.developer.apple.com/thread/121496
  ///
  /// Can be `null`
  @JsonKey(name: 'family_name')
  final String? familyName;

  /// The users email in case it was requested.
  /// You will need to provide the [AppleIDAuthorizationScopes.email] scope to the [AppleIDAuthorizationRequest] for requesting this information.
  ///
  /// This information will only be provided on the first authorizations.
  /// Upon further authorizations, you will only get the [userIdentifier],
  /// meaning you will need to store this data securely on your servers.
  /// For more information see: https://forums.developer.apple.com/thread/121496
  ///
  /// Can be `null`
  @JsonKey(name: 'email')
  final String? email;

  /// The verification code for the current authorization.
  ///
  /// This code should be used by your server component to validate the authorization with Apple within 5 minutes upon receiving it.
  @JsonKey(name: 'authorization_code')
  final String authorizationCode;

  /// A JSON Web Token (JWT) that securely communicates information about the user to your app.
  ///
  /// Can be `null`.
  @JsonKey(name: 'identity_token')
  final String? identityToken;

  /// The `state` parameter that was passed to the request.
  ///
  /// This data is not modified by Apple.
  ///
  /// Can be `null`
  @JsonKey(name: 'state')
  final String? state;

  AppleCredential(
      {required this.authorizationCode,
      this.userIdentifier,
      this.givenName,
      this.familyName,
      this.email,
      this.identityToken,
      this.state});

  factory AppleCredential.fromJson(Map<String, dynamic> json) => _$AppleCredentialFromJson(json);

  Map<String, dynamic> toJson() => _$AppleCredentialToJson(this);

  static FutureOr<AppleCredential> build(AuthorizationCredentialAppleID credential) {
    return AppleCredential(
        authorizationCode: credential.authorizationCode,
        userIdentifier: credential.userIdentifier,
        givenName: credential.givenName,
        familyName: credential.familyName,
        email: credential.email,
        identityToken: credential.identityToken,
        state: credential.state);
  }

  @override
  String toString() {
    return 'AppleCredential{userIdentifier: $userIdentifier, givenName: $givenName, familyName: $familyName, email: $email, authorizationCode: $authorizationCode, identityToken: $identityToken, state: $state}';
  }

  @override
  String get token => authorizationCode;

  @override
  AuthType get authType => AuthType.apple;
}

@JsonSerializable()
class FacebookPictureData {
  @JsonKey(name: 'width')
  final int width;

  @JsonKey(name: 'height')
  final int height;

  @JsonKey(name: 'is_silhouette', defaultValue: false)
  final bool isSilhouette;

  @JsonKey(name: 'url', defaultValue: '')
  final String url;

  FacebookPictureData({this.width = 0, this.height = 0, this.isSilhouette = false, this.url = ""});

  @override
  String toString() {
    return '${width}x$height $url}';
  }

  factory FacebookPictureData.fromJson(Map<String, dynamic> json) =>
      _$FacebookPictureDataFromJson(json);

  Map<String, dynamic> toJson() => _$FacebookPictureDataToJson(this);
}

@JsonSerializable()
class FacebookPicture {
  @JsonKey(name: 'data')
  final FacebookPictureData? data;

  FacebookPicture({this.data});

  @override
  String toString() {
    return '[FP] $data';
  }

  factory FacebookPicture.fromJson(Map<String, dynamic> json) => _$FacebookPictureFromJson(json);

  Map<String, dynamic> toJson() => _$FacebookPictureToJson(this);
}

@JsonSerializable()
class FacebookProfile {
  @JsonKey(name: 'id')
  final String? uid;

  @JsonKey(name: 'name')
  final String? displayName;

  @JsonKey(name: 'first_name')
  final String? firstName;

  @JsonKey(name: 'last_name')
  final String? lastName;

  @JsonKey(name: 'email')
  final String? email;

  @JsonKey(name: 'phoneNumber')
  final String? phoneNumber;

  @JsonKey(name: 'gender')
  final String? gender;

  @JsonKey(name: 'picture')
  final FacebookPicture? picture;

  FacebookProfile(
      {this.uid,
      this.displayName,
      this.firstName,
      this.lastName,
      this.gender,
      this.email,
      this.phoneNumber,
      this.picture});

  factory FacebookProfile.fromJson(Map<String, dynamic> json) => _$FacebookProfileFromJson(json);

  String get pictureUrl => picture?.data?.url ?? "";

  Map<String, dynamic> toJson() => _$FacebookProfileToJson(this);

  @override
  String toString() {
    return 'FacebookProfile{uid: $uid, displayName: $displayName, firstName: $firstName, lastName: $lastName, email: $email, phoneNumber: $phoneNumber, gender: $gender, picture: $picture}';
  }
}

class FacebookAccessTokenConvert
    implements JsonConverter<FacebookAccessToken, Map<String, dynamic>> {
  const FacebookAccessTokenConvert();

  @override
  FacebookAccessToken fromJson(Map<String, dynamic> json) {
    try {
      return FacebookAccessToken.fromMap(json);
    } catch (error) {
      return FacebookAccessToken.fromMap({});
    }
  }

  @override
  Map<String, dynamic> toJson(FacebookAccessToken accessToken) {
    return accessToken.toMap() ?? {};
  }
}

const facebookAccessTokenConvert = FacebookAccessTokenConvert();

@JsonSerializable(explicitToJson: true)
class FacebookCredential extends Credential {
  @JsonKey(name: 'facebook_access_token')
  @facebookAccessTokenConvert
  final FacebookAccessToken accessToken;

  @override
  String get token => accessToken.token;

  @override
  AuthType get authType => AuthType.facebook;

  FacebookCredential({required this.accessToken});

  factory FacebookCredential.fromJson(Map<String, dynamic> json) =>
      _$FacebookCredentialFromJson(json);

  Map<String, dynamic> toJson() => _$FacebookCredentialToJson(this);

  static FutureOr<FacebookCredential> build(FacebookAccessToken accessToken) {
    return FacebookCredential(accessToken: accessToken);
  }

  @override
  String toString() {
    return 'FacebookMetadata{accessToken: $accessToken}';
  }
}

@JsonSerializable()
class GoogleProfile {
  @JsonKey(name: 'id')
  final String? uid;

  @JsonKey(name: 'name')
  final String? displayName;

  @JsonKey(name: 'first_name')
  final String? firstName;

  @JsonKey(name: 'last_name')
  final String? lastName;

  @JsonKey(name: 'email')
  final String? email;

  @JsonKey(name: 'phone_number')
  final String? phoneNumber;

  @JsonKey(name: 'gender')
  final String? gender;

  @JsonKey(name: 'photoUrl')
  final String? photoUrl;

  GoogleProfile(
      {this.uid,
      this.displayName,
      this.firstName,
      this.lastName,
      this.gender,
      this.email,
      this.phoneNumber,
      this.photoUrl});

  factory GoogleProfile.fromJson(Map<String, dynamic> json) => _$GoogleProfileFromJson(json);

  Map<String, dynamic> toJson() => _$GoogleProfileToJson(this);

  @override
  String toString() {
    return 'GoogleProfile{uid: $uid, displayName: $displayName, firstName: $firstName, lastName: $lastName, email: $email, phoneNumber: $phoneNumber, gender: $gender, photoUrl: $photoUrl}';
  }
}

@JsonSerializable()
class GoogleAuth {
  @JsonKey(name: 'id_token')
  String idToken;

  @JsonKey(name: 'access_token')
  String? accessToken;

  @JsonKey(name: 'server_auth_code')
  String? serverAuthCode;

  GoogleAuth({required this.idToken, this.accessToken, this.serverAuthCode});

  factory GoogleAuth.fromJson(Map<String, dynamic> json) => _$GoogleAuthFromJson(json);

  Map<String, dynamic> toJson() => _$GoogleAuthToJson(this);

  @override
  String toString() {
    return 'GoogleAuth{idToken: $idToken, accessToken: $accessToken, serverAuthCode: $serverAuthCode}';
  }
}

@JsonSerializable(explicitToJson: true)
class GoogleCredential extends Credential {
  @JsonKey(name: 'google_auth')
  final GoogleAuth auth;

  @JsonKey(name: 'google_profile')
  final GoogleProfile? profile;

  String get idToken => auth.idToken;

  String? get accessToken => auth.accessToken;

  String? get serverAuthCode => auth.serverAuthCode;

  String? get uid => profile?.uid;

  String? get email => profile?.email;

  String? get name => profile?.displayName;

  GoogleCredential({required this.auth, this.profile});

  @override
  String get token => idToken;

  @override
  AuthType get authType => AuthType.google;

  factory GoogleCredential.fromJson(Map<String, dynamic> json) => _$GoogleCredentialFromJson(json);

  Map<String, dynamic> toJson() => _$GoogleCredentialToJson(this);

  static FutureOr<GoogleCredential> build(GoogleSignInAccount account) async {
    final googleAuth = await account.authentication;

    GoogleAuth auth = GoogleAuth(
        idToken: googleAuth.idToken!,
        accessToken: googleAuth.accessToken,
        serverAuthCode: account.serverAuthCode);

    GoogleProfile profile = GoogleProfile(
      uid: account.id,
      displayName: account.displayName,
      email: account.email,
      firstName: account.displayName,
      lastName: account.displayName,
      photoUrl: account.photoUrl,
    );
    return GoogleCredential(auth: auth, profile: profile);
  }

  @override
  String toString() {
    return 'GoogleMetadata{auth: $auth, profile: $profile}';
  }
}
