<?php

namespace App\Http\Controllers\Api\V1;

use App\Enums\UserRole;
use App\Enums\UserStatus;
use App\Exceptions\DomainException;
use App\Http\Controllers\Controller;
use App\Http\Requests\Auth\ForgotPasswordRequest;
use App\Http\Requests\Auth\LoginRequest;
use App\Http\Requests\Auth\ResetPasswordRequest;
use App\Models\Organization;
use App\Models\User;
use App\Services\AuditService;
use App\Services\BootstrapService;
use App\Support\ApiResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\URL;
use Illuminate\Support\Str;

class AuthController extends Controller
{
    use ApiResponse;

    public function __construct(
        private readonly BootstrapService $bootstrap,
        private readonly AuditService $audit,
    ) {}

    public function login(LoginRequest $request)
    {
        $query = User::query()
            ->with('organization')
            ->where('email', $request->string('email')->lower()->toString());

        if ($request->filled('organization_slug')) {
            $query->whereHas('organization', fn ($q) => $q->where('slug', $request->string('organization_slug')));
        }

        $users = $query->limit(2)->get();

        if ($users->count() !== 1 || ! Hash::check($request->string('password')->toString(), $users->first()?->password ?? '')) {
            throw new DomainException('INVALID_CREDENTIALS', 'The provided credentials are invalid.', 401);
        }

        $user = $users->first();

        if ($user->status !== UserStatus::ACTIVE || ! $user->organization?->is_active) {
            throw new DomainException('INVALID_CREDENTIALS', 'The provided credentials are invalid.', 401);
        }

        $deviceName = $request->string('device_name')->toString()
            ?: $request->string('device_id')->toString()
            ?: 'mobile-device';

        $expiresAt = config('sanctum.expiration')
            ? now()->addMinutes((int) config('sanctum.expiration'))
            : null;

        $token = $user->createToken($deviceName, ['*'], $expiresAt);
        $user->update(['last_login_at' => now()]);

        $this->audit->log('auth.login', $user, $user, null, [
            'device_name' => $deviceName,
        ]);

        return $this->success([
            'token' => $token->plainTextToken,
            'token_type' => 'Bearer',
            'expires_at' => $expiresAt?->toIso8601String(),
            'bootstrap' => $this->bootstrap->build($user),
        ], 'Login successful.');
    }

    public function forgotPassword(ForgotPasswordRequest $request)
    {
        $email = $request->string('email')->lower()->toString();
        $organization = Organization::query()
            ->where('slug', $request->string('organization_slug')->toString())
            ->where('is_active', true)
            ->first();

        $user = $organization
            ? User::query()
                ->where('organization_id', $organization->id)
                ->where('email', $email)
                ->whereIn('role', [UserRole::ADMIN->value, UserRole::DOCTOR->value, UserRole::ASSISTANT->value])
                ->where('status', UserStatus::ACTIVE->value)
                ->first()
            : null;

        if ($organization && $user) {
            $plainToken = Str::random(64);

            DB::table('password_reset_tokens')->updateOrInsert(
                ['organization_id' => $organization->id, 'email' => $email],
                ['token' => Hash::make($plainToken), 'created_at' => now()]
            );

            Mail::raw(
                "Use this Smart Clinic password reset token within 60 minutes:\n\n{$plainToken}\n\nIf you did not request this, ignore this email.",
                fn ($mail) => $mail->to($email)->subject('Smart Clinic password reset')
            );
        }

        // Always return the same response so account existence is not disclosed.
        return $this->success(null, 'If the account exists, password reset instructions have been sent.');
    }

    public function resetPassword(ResetPasswordRequest $request)
    {
        $email = $request->string('email')->lower()->toString();
        $organization = Organization::query()
            ->where('slug', $request->string('organization_slug')->toString())
            ->where('is_active', true)
            ->first();

        if (! $organization) {
            throw new DomainException('PASSWORD_RESET_INVALID', 'The password reset token is invalid or expired.', 422);
        }

        $reset = DB::table('password_reset_tokens')
            ->where('organization_id', $organization->id)
            ->where('email', $email)
            ->first();

        if (! $reset
            || ! $reset->created_at
            || now()->diffInMinutes($reset->created_at, true) > 60
            || ! Hash::check($request->string('token')->toString(), $reset->token)) {
            throw new DomainException('PASSWORD_RESET_INVALID', 'The password reset token is invalid or expired.', 422);
        }

        $user = User::query()
            ->where('organization_id', $organization->id)
            ->where('email', $email)
            ->first();

        if (! $user) {
            throw new DomainException('PASSWORD_RESET_INVALID', 'The password reset token is invalid or expired.', 422);
        }

        DB::transaction(function () use ($user, $organization, $email, $request) {
            $user->update(['password' => $request->string('password')->toString()]);
            $user->tokens()->delete();
            DB::table('password_reset_tokens')
                ->where('organization_id', $organization->id)
                ->where('email', $email)
                ->delete();

            $this->audit->log('auth.password_reset', $user, $user);
        });

        return $this->success(null, 'Password reset successfully. Please sign in again.');
    }

    public function sendEmailVerification(Request $request)
    {
        $user = $request->user();

        if ($user->email_verified_at) {
            return $this->success(['verified' => true], 'Email is already verified.');
        }

        $url = URL::temporarySignedRoute(
            'verification.verify',
            now()->addMinutes(60),
            ['id' => $user->id, 'hash' => sha1($user->email)]
        );

        Mail::raw(
            "Verify your Smart Clinic staff email by opening this secure link:\n\n{$url}\n\nThe link expires in 60 minutes.",
            fn ($mail) => $mail->to($user->email)->subject('Verify your Smart Clinic email')
        );

        return $this->success(['verified' => false], 'Verification email sent.');
    }

    public function verifyEmail(Request $request, int $id, string $hash)
    {
        $user = User::query()->findOrFail($id);

        if (! hash_equals(sha1($user->email), $hash)) {
            throw new DomainException('EMAIL_VERIFICATION_INVALID', 'The verification link is invalid.', 403);
        }

        if (! $user->email_verified_at) {
            $user->forceFill(['email_verified_at' => now()])->save();
            $this->audit->log('auth.email_verified', $user, $user);
        }

        return $this->success(['verified' => true], 'Email verified successfully.');
    }

    public function logout(Request $request)
    {
        $user = $request->user();
        $this->audit->log('auth.logout', $user, $user, null, [
            'token_name' => $user->currentAccessToken()?->name,
        ]);

        $user->currentAccessToken()?->delete();

        return $this->success(null, 'Logged out.');
    }

    public function logoutAll(Request $request)
    {
        $user = $request->user();
        $user->tokens()->delete();
        $this->audit->log('auth.logout_all', $user, $user);

        return $this->success(null, 'Logged out from all devices.');
    }
}
