<?php

namespace App\Services;

use Exception;
use Illuminate\Support\Str;
use App\Services\EmailService;
use App\Libs\Enum\UserTypeEnum;
use Illuminate\Auth\AuthManager;
use App\Libs\Enum\SocialSiteEnum;
use Tymon\JWTAuth\Facades\JWTAuth;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use App\Repositories\Interfaces\UserRepositoryInterface;
use App\Repositories\Interfaces\ProfileRepositoryInterface;
use App\Repositories\Interfaces\ProfessionalRepositoryInterface;
use App\Repositories\Interfaces\VerificationCodeRepositoryInterface;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\DB;
use Symfony\Component\Mailer\Exception\TransportExceptionInterface;
use Throwable;

class AuthService
{
    /**
     * @var UserRepositoryInterface
     */
    protected $userRepository;

    /**
     * @var ProfileRepositoryInterface
     */
    protected $profileRepository;

    /**
     * @var ProfessionalRepositoryInterface
     */
    protected $professionalRepository;

    /**
     * @var AuthManager
     */
    protected $auth;

    /**
     * @var VerificationCodeRepositoryInterface
     */
    protected $verificationCodeRepository;

    /**
     * @var EmailService
     */
    protected $emailService;

    protected AppleIdentityTokenService $appleIdentityTokenService;

    protected GoogleIdentityTokenService $googleIdentityTokenService;

    private $response = [
        'success' => true,
        'message' => null
    ];

    /**
     * @param AuthManager $auth
     * @param UserRepositoryInterface $userRepository
     * @param ProfileRepositoryInterface $profileRepository
     * @param ProfessionalRepositoryInterface $professionalRepository
     * @param VerificationCodeRepositoryInterface $verificationCodeRepository
     * @param EmailService $emailService
     */
    public function __construct(
        AuthManager $auth,
        UserRepositoryInterface $userRepository,
        ProfileRepositoryInterface $profileRepository,
        ProfessionalRepositoryInterface $professionalRepository,
        VerificationCodeRepositoryInterface $verificationCodeRepository,
        EmailService $emailService,
        AppleIdentityTokenService $appleIdentityTokenService,
        GoogleIdentityTokenService $googleIdentityTokenService
    ) {
        $this->auth = $auth;
        $this->userRepository = $userRepository;
        $this->profileRepository = $profileRepository;
        $this->professionalRepository = $professionalRepository;
        $this->verificationCodeRepository = $verificationCodeRepository;
        $this->emailService = $emailService;
        $this->appleIdentityTokenService = $appleIdentityTokenService;
        $this->googleIdentityTokenService = $googleIdentityTokenService;
    }

    /**
     * @param array $payload
     *
     * @return array
     */
    public function register(array $payload): array
    {
        try {
            DB::beginTransaction();
            $socialSite = $payload['social_site'];
            
            if ($socialSite === SocialSiteEnum::desxpert->value) {
                $this->registerViaDesxpert($payload);
            } else if ($socialSite === SocialSiteEnum::google->value) {
                $user = $this->registerViaGoogle($payload);
                if ($user) {
                    if ($user->role_id === UserTypeEnum::fromKey('PROFESSIONAL')->value) {
                        $professional = $this->professionalRepository->getByColumn('user_id', $user->id);
                        if (!$professional || !$professional->has_applied) {
                            DB::commit();
                            return [
                                'success' => true,
                                'message' => 'Incomplete application',
                                'data' => [
                                    'user_type' => "PROFESSIONAL",
                                    'first_name' => $user->profile->first_name,
                                    'last_name' => $user->profile->last_name,
                                    'email' => $user->email
                                ],
                                'status' => 201
                            ];
                        }

                        if ($professional->status === 'pending') {
                            DB::commit();
                            return [
                                'success' => false,
                                'message' => 'Application is under review',
                                'status' => 403
                            ];
                        }
                    }

                    $token = $this->respondWithToken($user);
                    DB::commit();

                    return [
                        'success' => true,
                        'message' => 'Login successful',
                        'access_token' => $token,
                    ];
                }
            } else if ($socialSite === SocialSiteEnum::apple->value) {
                $user = $this->registerViaApple($payload);
                if ($user) {
                    if ($user->role_id === UserTypeEnum::fromKey('PROFESSIONAL')->value) {
                        $professional = $this->professionalRepository->getByColumn('user_id', $user->id);
                        if (!$professional || !$professional->has_applied) {
                            DB::commit();
                            return [
                                'success' => true,
                                'message' => 'Incomplete application',
                                'data' => [
                                    'user_type' => "PROFESSIONAL",
                                    'first_name' => $user->profile->first_name,
                                    'last_name' => $user->profile->last_name,
                                    'email' => $user->email
                                ],
                                'status' => 201
                            ];
                        }

                        if ($professional->status === 'pending') {
                            DB::commit();
                            return [
                                'success' => false,
                                'message' => 'Application is under review',
                                'status' => 403
                            ];
                        }
                    }

                    $token = $this->respondWithToken($user);
                    DB::commit();

                    return [
                        'success' => true,
                        'message' => 'Login successful',
                        'access_token' => $token,
                    ];
                }
            }
            DB::commit();
            $this->response['message'] = "Data created";
            return $this->response;
        } catch (TransportExceptionInterface $e) {
            DB::rollBack();
            app('log')->error('Registration activation email delivery failed', [
                'errorMessage' => $e->getMessage(),
            ]);

            return [
                'success' => false,
                'message' => 'We could not send your verification email. Please try registering again later.',
                'status' => 503,
            ];
        } catch (Exception $e) {
            DB::rollBack();
            app('log')->error("Error", ['errorMessage' => $e->getMessage()]);
            $this->response['success'] = false;
            $this->response['message'] = $e->getMessage();
            return $this->response;
        }
    }

    /**
     * @param array $payload
     * 
     * @return array
     */
    public function activation(array $payload): array
    {
        try {
            DB::beginTransaction();
            $payload['type'] = "account-activation";

            $verify = $this->verificationCodeRepository->verifyCode($payload);

            if (!$verify) {
                $this->response['success'] = false;
                $this->response['message'] = "Invalid activation code.";
                $this->response['status'] = 401;
                return $this->response;
            }

            $now = strtotime(now());
            $validity = strtotime($verify->valid_until);

            if ($validity < $now) {
                $this->response['success'] = false;
                $this->response['message'] = "Activation code is expired";
                $this->response['status'] = 401;
                return $this->response;
            }

            $user = $this->userRepository->getByColumn('email', $payload['email']);

            $this->userRepository->update(
                ['status' => 'active', 'verified' => 1], 
                $user->id
            );

            $this->verificationCodeRepository->deleteByColumn('email', $user->email);

            $this->response['message'] = "Account verified";

            $userType = match($user->role_id) {
                UserTypeEnum::fromKey('CLIENT')->value => 'CLIENT',
                UserTypeEnum::fromKey('PROFESSIONAL')->value => 'PROFESSIONAL',
                UserTypeEnum::fromKey('ADMIN')->value => 'ADMIN',
                default => throw new Exception("Invalid Role")
            };

            $this->response['data'] = [
                'user_type' => $userType,
                'first_name' => $user->profile->first_name,
                'last_name' => $user->profile->last_name,
                'email' => $user->email
            ];
            DB::commit();
            return $this->response;
        } catch (Exception $e) {
            DB::rollBack();
            app('log')->error("Error", ['errorMessage' => $e->getMessage()]);
            $this->response['success'] = false;
            $this->response['message'] = "Bad Request";
            $this->response['status'] = 400;
            return $this->response;
        }
    }

    /**
     * @param array $payload
     * 
     * @return array
     */
    public function login(array $payload): array
    {
        $response = [];
        $response['redirect'] = '';
        $response['status']  =  '';
        $response['message']  =  '';
        
        $socialSite = $payload['social_site'];

        if ($socialSite === SocialSiteEnum::desxpert->value) {
            $credentials = [
                'username' => $payload['username'],
                'password' => $payload['password'],
                'status' => 'active',
                'social_site' => 'desxpert'
            ];

            if (filter_var($payload['username'], FILTER_VALIDATE_EMAIL)) {
                $credentials['email'] = $payload['username'];
                unset($credentials['username']);
            }
            if ($this->auth->attempt($credentials)) {
                $user = Auth::user();
            } else {
                return [
                    'success' => false,
                    'message' => 'Invalid Credentials',
                    'status' => 401
                ];
            }
        } else if ($socialSite === SocialSiteEnum::google->value) {
            try {
                $accountInfo = $this->googleIdentityTokenService->verify($payload['access_token']);
            } catch (Exception $exception) {
                return [
                    'success' => false,
                    'message' => $exception->getMessage(),
                    'status' => 401
                ];
            }
            
            $user = $this->userRepository->searchUserSite($accountInfo->email, 'google');
            
            if (!$user) {
                return [
                    'success' => false,
                    'message' => "Account doesn't exist",
                    'status' => 404
                ];
            }
        } else if ($socialSite === SocialSiteEnum::apple->value) {
            try {
                $accountInfo = $this->appleIdentityTokenService->verify(
                    $payload['access_token'],
                    $payload['nonce']
                );
            } catch (Exception $exception) {
                return [
                    'success' => false,
                    'message' => $exception->getMessage(),
                    'status' => 401
                ];
            }

            $user = $this->userRepository->searchUserSite($accountInfo->email, SocialSiteEnum::apple->value);
            if (!$user) {
                return [
                    'success' => false,
                    'message' => "Account doesn't exist",
                    'status' => 404
                ];
            }
        } else {
            return [
                'success' => false,
                'message' => 'Social site not supported',
                'status' => 400
            ];
        }

        if ($user->role_id === UserTypeEnum::fromKey('PROFESSIONAL')->value) {
            $professional = $this->professionalRepository->getByColumn('user_id', $user->id);
            if (!$professional || !$professional->has_applied) {
                return [
                    'success' => true,
                    'message' => 'Incomplete application',
                    'data' => [
                        'user_type' => "PROFESSIONAL",
                        'first_name' => $user->profile->first_name,
                        'last_name' => $user->profile->last_name,
                        'email' => $user->email
                    ],
                    'status' => 201
                ];
            }

            if ($professional->status === 'pending') {
                return [
                    'success' => false,
                    'message' => 'Application is under review',
                    'status' => 403
                ];
            }
        }

        $token = $this->respondWithToken($user);

        return [
            'success' => true,
            'message' => 'Login successful',
            'access_token' => $token,
        ];
    }

    /**
     * Rotate an access token while it is still inside JWT_REFRESH_TTL.
     */
    public function refreshToken(?string $token): array
    {
        if (empty($token)) {
            return [
                'success' => false,
                'message' => 'Refresh token is required',
                'status' => 401,
            ];
        }

        try {
            $newToken = JWTAuth::setToken($token)->refresh();

            return [
                'success' => true,
                'message' => 'Token refreshed',
                'access_token' => $newToken,
                'token_type' => 'bearer',
                'expires_in' => null,
            ];
        } catch (Throwable $exception) {
            app('log')->warning('Unable to refresh JWT', [
                'errorMessage' => $exception->getMessage(),
            ]);

            return [
                'success' => false,
                'message' => 'Your session has expired. Please log in again.',
                'status' => 401,
            ];
        }
    }

    /**
     * @param array $payload
     * 
     * @return array
     */
    public function forgotPassword(array $payload): array
    {
        try {
            DB::beginTransaction();
            $email = $payload['email'];
            
            $user = $this->userRepository->searchUserSite($email, 'desxpert');

            if (!$user) {
                return [
                    'success' => false,
                    'message' => "Account doesn't exist",
                    'status' => 404
                ];
            }

            $this->verificationCodeRepository->deleteByColumn('email', $email);

            $verificationCode = $this->generateVerificationCode();

            $verificationPayload = [
                'email' => $email,
                'type' => 'forgot-password',
                'code' => $verificationCode,
                'valid_until' => now()->plus(minutes: 5)
            ];

            $this->verificationCodeRepository->create($verificationPayload);

            $emailInfo = [
                'name' => $user->profile->first_name,
                'email' => $email,
                'code' => $verificationCode
            ];

            $this->emailService->sendEmail('forgot-password', $emailInfo);
        
            $this->userRepository->update(['force_change_password' => 1, 'password' => null], $user->id);

            $this->response['message'] = "Email Sent";
            DB::commit();
            return $this->response;
        } catch (Exception $e) {
            DB::rollBack();
            app('log')->error("Error", ['errorMessage' => $e->getMessage()]);
            $this->response['success'] = false;
            $this->response['message'] = "Bad Request";
            $this->response['status'] = 400;
            return $this->response;
        }
    }

    /**
     * @param array $payload
     * 
     * @return array
     */
    public function resendCode(array $payload): array
    {
        try {
            DB::beginTransaction();
            $email = $payload['email'];
            $type = $payload['type'];

            $codeType = match($type) {
                'account-activation' => 'account-activation',
                'forgot-password' => 'forgot-password',
                default => false
            };

            if (!$codeType) {
                $this->response['success'] = false;
                $this->response['message'] = "Invalid code type";
                $this->response['status'] = 400;
                return $this->response;
            }

            $this->verificationCodeRepository->deleteByColumn('email', $email);
            
            $verificationCode = $this->generateVerificationCode();

            $verificationPayload = [
                'email' => $email,
                'type' => $codeType,
                'code' => $verificationCode,
                'valid_until' => now()->plus(minutes: 5)
            ];

            $this->verificationCodeRepository->create($verificationPayload);

            $firstName = $payload['first_name'] ?? null;
            if (!$firstName) {
                $user = $this->userRepository->getByColumn('email', $email);
                $firstName = $user->profile->first_name;
            }
            $emailInfo = [
                'name' => $firstName,
                'email' => $email,
                'code' => $verificationCode
            ];

            $this->emailService->sendEmail($codeType, $emailInfo);
            
            $this->response['message'] = "Email Sent";
            DB::commit();
            return $this->response;
        } catch (Exception $e) {
            DB::rollBack();
            app('log')->error("Error", ['errorMessage' => $e->getMessage()]);
            $this->response['success'] = false;
            $this->response['message'] = "Bad Request";
            $this->response['status'] = 400;
            return $this->response;
        }
    }

    /**
     * @param array $payload
     * 
     * @return array
     */
    public function changePassword(array $payload): array
    {
        try {
            $email = $payload['email'];
            $user = $this->userRepository->getByColumn('email', $email);
            $updatePayload = [
                'password' => Hash::make($payload['password']),
                'force_change_password' => 0,
                'status' => 'active',
                'verified' => 1
            ];
            $this->userRepository->update($updatePayload, $user->id);
            $this->response['message'] = "Password has been changed successfully";
            return $this->response;
        } catch (Exception $e) {
            app('log')->error("Error", ['errorMessage' => $e->getMessage()]);
            $this->response['success'] = false;
            $this->response['message'] = "Bad Request";
            $this->response['status'] = 400;
            return $this->response;
        }
    }

    /**
     * @param array $payload
     * 
     * @return array
     */
    public function verifyCode(array $payload): array
    {
        $verify = $this->verificationCodeRepository->verifyCode($payload);

        if (!$verify) {
            $this->response['success'] = false;
            $this->response['message'] = "Invalid code";
            return $this->response;
        }

        $now = strtotime(now());
        $validity = strtotime($verify->valid_until);

        if ($validity < $now) {
            $this->response['success'] = false;
            $this->response['message'] = "Forgot password code is expired";
            $this->response['status'] = 400;
            return $this->response;
        }

        $this->verificationCodeRepository->deleteByColumn('email', $payload['email']);

        $this->response['message'] = "Code Verified";

        return $this->response;
    }

    /**
     * Get the token array structure.
     *
     * @param  object $userInfo
     *
     * @return string
     */
    private function respondWithToken(object $user): string
    {
        $userInfo = [
            'first_name' => ucfirst($user->profile->first_name),
            'last_name' => ucfirst($user->profile->last_name),
            'full_name' => $user->profile->full_name,
            'email' => $user->email,
            'uuid' => $user->uuid,
            'user_type' => strtoupper($user->role->name)
        ];
        $token = auth()->claims(['user_info' => $userInfo])->login($user);
        return $token;
    }

    /**
     * @return string
     */
    private function generateVerificationCode(): string
    {
        $length = 6;

        $min = 10**($length - 1);
        $max = (10**$length) - 1;

        try {
            $code = random_int($min, $max);
            return (string) $code;
        } catch (Exception $e) {
           app('log')->error("Error", ['errorMessage' => $e->getMessage()]);
           return "Error";
        }
    }

    /**
     * @param array $payload
     * 
     * @return void
     */
    private function registerViaDesxpert(array $payload): void
    {
        try {
            $email = $payload['email'];
            $username = $payload['username'];
            $firstName = $payload['first_name'];
            $lastName = $payload['last_name'];

            $userPayload = [
                'uuid' => Str::uuid(),
                'username' => $username,
                'role_id' => UserTypeEnum::fromKey($payload['user_type'])->value,
                'email' => $email,
                'password' => Hash::make($payload['password']),
                'app_id' => config('desxpert.app_id'),
                'social_site' => SocialSiteEnum::desxpert->value
            ];
            
            $user = $this->userRepository->create($userPayload);

            $profilePayload = [
                'user_id' => $user->id,
                'first_name' => $firstName,
                'last_name' => $lastName,
                'full_name' => $firstName.' '.$lastName
            ];

            $this->profileRepository->create($profilePayload);

            $verificationCode = $this->generateVerificationCode();

            $verificationPayload = [
                'email' => $email,
                'type' => 'account-activation',
                'code' => $verificationCode,
                'valid_until' => now()->plus(minutes: 5)
            ];

            $this->verificationCodeRepository->create($verificationPayload);

            $emailInfo = [
                'name' => $firstName,
                'email' => $email,
                'code' => $verificationCode
            ];

            $this->emailService->sendEmail('account-activation', $emailInfo);
        } catch (TransportExceptionInterface $e) {
            // Preserve the transport failure so registration can return a safe,
            // actionable message after rolling back the account and code.
            throw $e;
        } catch (Exception $e) {
            app('log')->error("Error", ['errorMessage' => $e->getMessage()]);
            throw new Exception("Error account registration via desxpert");
        }
    }

    /**
     * @param array $payload
     * 
     * @return model
     */
    private function registerViaGoogle(array $payload): Model
    {
        try {
            $accountInfo = $this->googleIdentityTokenService->verify($payload['access_token']);
            $isEmailExist = $this->userRepository->getByColumn('email', $accountInfo->email);
            if ($isEmailExist) {
                throw new Exception("Email already exist");
            }
            
            $userPayload = [
                'uuid' => Str::uuid(),
                'role_id' => UserTypeEnum::fromKey($payload['user_type'])->value,
                'email' => $accountInfo->email,
                'status' => 'active',
                'verified' => true,
                'app_id' => config('desxpert.app_id'),
                'social_site' => SocialSiteEnum::google->value
            ];
            
            $user = $this->userRepository->create($userPayload);
            
            $profilePayload = [
                'user_id' => $user->id,
                'first_name' => $accountInfo->given_name,
                'last_name' => $accountInfo->family_name ?? '',
                'full_name' => $accountInfo->name
            ];

            $this->profileRepository->create($profilePayload);

            return $user;

        } catch (Exception $e) {
            app('log')->error("Error", ['errorMessage' => $e->getMessage()]);
            throw new Exception("Error account registration via google: ".$e->getMessage());
        }
    }

    private function registerViaApple(array $payload): Model
    {
        try {
            $accountInfo = $this->appleIdentityTokenService->verify(
                $payload['access_token'],
                $payload['nonce']
            );
            $email = (string) $accountInfo->email;

            if ($this->userRepository->getByColumn('email', $email)) {
                throw new Exception('Email already exist');
            }

            $firstName = trim((string) ($payload['first_name'] ?? '')) ?: 'Apple';
            $lastName = trim((string) ($payload['last_name'] ?? '')) ?: 'User';

            $user = $this->userRepository->create([
                'uuid' => Str::uuid(),
                'role_id' => UserTypeEnum::fromKey($payload['user_type'])->value,
                'email' => $email,
                'status' => 'active',
                'verified' => true,
                'app_id' => config('desxpert.app_id'),
                'social_site' => SocialSiteEnum::apple->value
            ]);

            $this->profileRepository->create([
                'user_id' => $user->id,
                'first_name' => $firstName,
                'last_name' => $lastName,
                'full_name' => trim($firstName.' '.$lastName)
            ]);

            return $user;
        } catch (Exception $exception) {
            app('log')->error('Apple registration failed', ['errorMessage' => $exception->getMessage()]);
            throw new Exception('Error account registration via apple: '.$exception->getMessage());
        }
    }

    /**
     * @return object
     */
    public function decodeToken(): object
    {
        $token = JWTAuth::parseToken();
        return  $token->getPayload();
    }
}
