<?php

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use App\Models\DeviceToken;
use App\Models\User;
use App\Services\Auth\ApiLoginChallengeService;
use App\Services\Auth\ApiTokenService;
use App\Services\Hr\EmployeeAccessRevocationService;
use App\Services\TenantRoleService;
use App\Services\TwoFactorService;
use Illuminate\Auth\Events\PasswordReset;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Password;
use Illuminate\Support\Str;
use Illuminate\Validation\Rules\Password as PasswordRule;
use Illuminate\Validation\ValidationException;

class AuthController extends Controller
{
    public function login(Request $request, TwoFactorService $twoFactor, ApiLoginChallengeService $challenges, ApiTokenService $apiTokens, \App\Services\Auth\TenantLoginResolver $loginResolver): JsonResponse
    {
        $request->validate([
            'company' => 'nullable|string|max:100',
            'email' => 'required|email',
            'password' => 'required',
        ]);

        $email = (string) $request->email;
        $password = (string) $request->password;
        $company = $request->input('company');

        $user = $loginResolver->findUserByEmailAndPassword($company, $email, $password);
        if (! $user) {
            if ($loginResolver->hasMultiplePasswordMatches($company, $email, $password)) {
                throw ValidationException::withMessages([
                    'email' => ['Unable to Login. Please contact your administrator.'],
                ]);
            }
            throw ValidationException::withMessages(['email' => ['The provided credentials are incorrect.']]);
        }

        $this->ensureAccountCanAuthenticate($user);

        TenantRoleService::assignEmployeeRoleIfMissing($user);

        if ($twoFactor->requiresChallenge($user)) {
            return $this->twoFactorChallengeResponse($user, $twoFactor, $challenges);
        }

        return $this->tokenResponse($user, $apiTokens);
    }

    public function verifyTwoFactor(Request $request, TwoFactorService $twoFactor, ApiLoginChallengeService $challenges, ApiTokenService $apiTokens): JsonResponse
    {
        $request->validate([
            'challenge_token' => 'required|string',
            'code' => 'required|string|size:6',
        ]);

        $user = $challenges->user($request->input('challenge_token'));
        if (! $user) {
            throw ValidationException::withMessages([
                'challenge_token' => ['Challenge expired. Please sign in again.'],
            ]);
        }

        $this->ensureAccountCanAuthenticate($user);

        if ($twoFactor->isVerifyLockedOut($user)) {
            return response()->json([
                'message' => 'Too many failed attempts. Try again later.',
                'error' => 'two_factor_locked',
            ], 429);
        }

        if (! $twoFactor->verifyLoginCode($user, $request->input('code'))) {
            $twoFactor->recordFailedVerify($user);

            throw ValidationException::withMessages([
                'code' => ['Invalid or expired verification code.'],
            ]);
        }

        $twoFactor->clearLoginCode($user);
        $twoFactor->clearFailedVerifies($user);
        $challenges->forget($request->input('challenge_token'));

        return $this->tokenResponse($user, $apiTokens);
    }

    public function resendTwoFactor(Request $request, TwoFactorService $twoFactor, ApiLoginChallengeService $challenges): JsonResponse
    {
        $request->validate([
            'challenge_token' => 'required|string',
        ]);

        $user = $challenges->user($request->input('challenge_token'));
        if (! $user) {
            throw ValidationException::withMessages([
                'challenge_token' => ['Challenge expired. Please sign in again.'],
            ]);
        }

        if ($twoFactor->method($user) !== TwoFactorService::METHOD_EMAIL) {
            return response()->json([
                'message' => 'Resend is only available for email verification.',
                'error' => 'resend_not_available',
            ], 422);
        }

        $twoFactor->sendLoginCode($user);

        return response()->json(['message' => 'A new verification code has been sent.']);
    }

    protected function twoFactorChallengeResponse(User $user, TwoFactorService $twoFactor, ApiLoginChallengeService $challenges): JsonResponse
    {
        if ($twoFactor->beginLoginChallenge($user) === 'enroll') {
            return response()->json([
                'message' => 'Two-factor enrollment is required. Complete setup in the web application first.',
                'error' => 'two_factor_enrollment_required',
            ], 403);
        }

        return response()->json([
            'requires_two_factor' => true,
            'challenge_token' => $challenges->create($user),
            'method' => $twoFactor->method($user),
            'masked_email' => $this->maskEmail($user->email),
        ]);
    }

    protected function tokenResponse(User $user, ApiTokenService $apiTokens): JsonResponse
    {
        $token = $apiTokens->plainTextToken($user);

        return response()->json([
            'user' => $user->load(['company', 'role', 'department', 'designation']),
            'token' => $token,
            'token_type' => 'Bearer',
        ]);
    }

    protected function maskEmail(?string $email): ?string
    {
        if (! $email || ! str_contains($email, '@')) {
            return $email;
        }

        [$local, $domain] = explode('@', $email, 2);
        $hidden = strlen($local) <= 1
            ? str_repeat('•', 3)
            : substr($local, 0, 1).str_repeat('•', max(2, strlen($local) - 1));

        return $hidden.'@'.$domain;
    }

    /**
     * @throws ValidationException
     */
    protected function ensureAccountCanAuthenticate(User $user): void
    {
        app(EmployeeAccessRevocationService::class)->deactivateIfLastWorkingDatePassed($user);
        $user->refresh();

        if ($user->status === User::STATUS_INACTIVE) {
            throw ValidationException::withMessages([
                'email' => ['Please contact the administrator.'],
            ]);
        }

        if (! $user->allow_login) {
            throw ValidationException::withMessages([
                'email' => ['Login is disabled for this account.'],
            ]);
        }
    }

    public function register(Request $request): JsonResponse
    {
        $request->validate([
            'name' => 'required|string|max:191',
            'email' => 'required|email|max:191',
            'password' => 'required|string|min:8|confirmed',
            'company_name' => 'required|string|max:191',
        ]);

        // Company creation and user registration should be delegated to SubscriptionService
        return response()->json(['message' => 'Use company registration endpoint.'], 422);
    }

    public function logout(Request $request): JsonResponse
    {
        $user = $request->user();
        $token = $request->input('fcm_token') ?? $request->input('token');

        $deviceTokens = DeviceToken::query()->where('user_id', $user->id);
        if (is_string($token) && $token !== '') {
            $deviceTokens->where('token', $token);
        }
        $deviceTokens->delete();

        $user->currentAccessToken()->delete();

        return response()->json(['message' => 'Logged out.']);
    }

    public function deleteAccount(Request $request): JsonResponse
    {
        $user = $request->user();
        $alreadyInactive = $user->status === User::STATUS_INACTIVE;

        if (! $alreadyInactive) {
            $user->forceFill(['status' => User::STATUS_INACTIVE])->save();
        }

        DeviceToken::query()->where('user_id', $user->id)->delete();
        $user->tokens()->delete();

        return response()->json([
            'message' => $alreadyInactive
                ? 'Account is already inactive.'
                : 'Account deactivated successfully.',
        ]);
    }

    public function user(Request $request): JsonResponse
    {
        return response()->json($request->user()->load(['company', 'role', 'department', 'designation', 'location', 'shift']));
    }

    public function forgotPassword(Request $request, \App\Services\Auth\TenantLoginResolver $loginResolver): JsonResponse
    {
        $request->validate([
            'company' => 'nullable|string|max:100',
            'email' => 'required|email',
        ]);

        $credentials = $loginResolver->passwordResetCredentials(
            $request->input('company'),
            (string) $request->email
        );

        if ($credentials) {
            $user = $loginResolver->findUser($request->input('company'), (string) $request->email);
            if ($user && $user->allow_login && $user->status !== User::STATUS_INACTIVE && ! $user->hasPassedLastWorkingDate()) {
                Password::sendResetLink($credentials);
            }
        }

        return response()->json([
            'message' => 'If an account exists for that address, a password reset link has been sent.',
        ]);
    }

    public function resetPassword(Request $request, \App\Services\Auth\TenantLoginResolver $loginResolver): JsonResponse
    {
        $request->validate([
            'token' => 'required|string',
            'company' => 'nullable|string|max:100',
            'email' => 'required|email',
            'password' => ['required', 'confirmed', PasswordRule::min(8)],
        ]);

        $user = $loginResolver->findUser($request->input('company'), (string) $request->email);
        if (! $user || ! $user->allow_login || $user->status === User::STATUS_INACTIVE || $user->hasPassedLastWorkingDate()) {
            return response()->json([
                'message' => 'This account cannot reset its password.',
                'errors' => ['email' => ['This account cannot reset its password.']],
            ], 422);
        }

        $status = Password::reset(
            [
                'email' => $user->email,
                'company_id' => $user->company_id,
                'password' => $request->password,
                'password_confirmation' => $request->password_confirmation,
                'token' => $request->token,
            ],
            function (User $user, string $password): void {
                $user->forceFill([
                    'password' => $password,
                    'remember_token' => Str::random(60),
                ])->save();

                $user->tokens()->delete();

                event(new PasswordReset($user));
            }
        );

        if ($status === Password::PASSWORD_RESET) {
            return response()->json(['message' => 'Password has been reset. You can sign in now.']);
        }

        return response()->json([
            'message' => __($status),
            'errors' => ['email' => [__($status)]],
        ], 422);
    }
}
