<?php

namespace App\Services\Auth;

use App\Entities\User;
use App\Exceptions\ValidationException;
use App\Models\UserModel;
use App\Models\AgencyModel;
use App\Services\Support\ActivityLogger;
use Config\Database;

/**
 * Owns all authentication business rules. Controllers (web AuthController,
 * API Auth controller) both call into this — no duplicated logic between
 * the two surfaces.
 */
class AuthService
{
    private UserModel $userModel;
    private AgencyModel $agencyModel;
    private JwtService $jwt;
    private ActivityLogger $activity;

    public function __construct()
    {
        $this->userModel   = new UserModel();
        $this->agencyModel = new AgencyModel();
        $this->jwt         = new JwtService();
        $this->activity    = new ActivityLogger();
    }

    public function registerCandidate(array $input): array
    {
        $roleId = $this->roleIdFor('candidate');

        if (empty($input['phone'])) {
            throw new ValidationException(['phone' => 'Mobile number is required.']);
        }

        $requireVerification = $this->otpVerificationRequired();

        $userId = $this->userModel->insert([
            'role_id'       => $roleId,
            'agency_id'     => null,
            'name'          => $input['name'],
            'email'         => $input['email'],
            'phone'         => $input['phone'],
            'password_hash' => password_hash($input['password'], PASSWORD_ARGON2ID),
            // Phone/email are always mandatory and unique regardless of this
            // setting — only whether OTP confirmation is REQUIRED before
            // the account activates is configurable (Settings → Public
            // Profiles → Require OTP Verification). Off by default since a
            // fresh install has no SMS/email gateway configured yet, and
            // forcing verification with no way to deliver codes would
            // strand every new signup.
            'status'        => $requireVerification ? 'pending_verification' : 'active',
        ], true);

        if ($userId === false) {
            throw new ValidationException($this->userModel->errors());
        }

        // Auto-create the empty candidate_profiles row — every candidate
        // account has exactly one profile from the moment of registration.
        (new \App\Services\Candidate\CandidateProfileService())->createEmptyProfile($userId);

        // Auto-assign a username so the public profile URL works
        // immediately — the candidate can customize it later from My
        // Profile, subject to the admin-configured change-rate limit.
        $usernameService = new \App\Services\PublicProfile\UsernameService();
        $usernameService->setInitial($userId, $usernameService->suggestFromName($input['name']));

        $otpInfo = null;
        if ($requireVerification) {
            $otp = new OtpService();
            $otpInfo = [
                'email'  => $otp->generateAndSend($userId, 'email', $input['email']),
                'mobile' => $otp->generateAndSend($userId, 'mobile', $input['phone']),
            ];
        }

        $this->activity->log($userId, null, 'candidate', 'registration', ['user_id' => $userId, 'type' => 'candidate']);

        return [
            'user' => $this->userModel->findWithRole($userId),
            'otp'  => $otpInfo,
        ];
    }

    /**
     * Settings → Public Profiles → "Require OTP Verification" (group
     * 'profile', key 'require_otp_verification'). Defaults to false (skip)
     * since a fresh install has no SMS/email gateway configured.
     */
    private function otpVerificationRequired(): bool
    {
        $row = Database::connect()->table('settings')->where('group', 'profile')->where('key', 'require_otp_verification')->get()->getRow();
        return $row ? ((string) $row->value === '1') : false;
    }

    public function registerAgencyOwner(array $input): array
    {
        if (empty($input['phone'])) {
            throw new ValidationException(['phone' => 'Mobile number is required.']);
        }

        $requireVerification = $this->otpVerificationRequired();

        $db = Database::connect();
        $db->transStart();

        $roleId = $this->roleIdFor('agency_owner');

        $userId = $this->userModel->insert([
            'role_id'       => $roleId,
            'agency_id'     => null, // set after agency is created below
            'name'          => $input['owner_name'],
            'email'         => $input['email'],
            'phone'         => $input['phone'],
            'password_hash' => password_hash($input['password'], PASSWORD_ARGON2ID),
            'status'        => $requireVerification ? 'pending_verification' : 'active',
        ], true);

        $slug = $this->uniqueSlug($input['company_name']);

        $agencyId = $this->agencyModel->insert([
            'company_name' => $input['company_name'],
            'slug'         => $slug,
            'email'        => ! empty($input['company_email']) ? $input['company_email'] : $input['email'],
            'phone'        => $input['company_phone'] ?? null,
            'industry'     => $input['industry'] ?? null,
            'company_size' => $input['company_size'] ?? null,
            'owner_user_id'=> $userId,
            'status'       => 'active',
        ], true);

        $this->userModel->update($userId, ['agency_id' => $agencyId]);

        // Auto-assign a username for the agency owner too (public agency
        // profile is reachable via the owner's username, per spec).
        $usernameService = new \App\Services\PublicProfile\UsernameService();
        $usernameService->setInitial($userId, $usernameService->suggestFromName($input['company_name']));

        $db->transComplete();

        if ($db->transStatus() === false) {
            throw new \RuntimeException('Agency registration failed — transaction rolled back.');
        }

        // Every new agency starts on the Free plan immediately.
        (new \App\Services\Billing\SubscriptionService())->startFreeTrial($agencyId);

        // Welcome bonus so a brand-new agency can try contact-reveal/resume
        // download/messaging immediately without needing to buy credits first.
        (new \App\Services\Billing\CreditService())->credit($agencyId, 100, 'bonus', 'agency_registration', $agencyId);

        $this->activity->log($userId, $agencyId, 'agency_owner', 'registration', ['agency_id' => $agencyId]);

        $otpInfo = null;
        if ($requireVerification) {
            $otp = new OtpService();
            $otpInfo = [
                'email'  => $otp->generateAndSend($userId, 'email', $input['email']),
                'mobile' => $otp->generateAndSend($userId, 'mobile', $input['phone']),
            ];
        }

        return [
            'user'   => $this->userModel->findWithRole($userId),
            'agency' => $this->agencyModel->find($agencyId),
            'otp'    => $otpInfo,
        ];
    }

    public function attemptLogin(string $email, string $password, string $ip, string $device, string $browser): ?User
    {
        $user = $this->userModel->findByEmailWithRole($email);

        $db = Database::connect();

        if (! $user || ! password_verify($password, $user->password_hash)) {
            if ($user) {
                $db->table('login_history')->insert([
                    'user_id'    => $user->id,
                    'ip_address' => $ip,
                    'device'     => $device,
                    'browser'    => $browser,
                    'status'     => 'failed',
                    'created_at' => date('Y-m-d H:i:s'),
                ]);
            }
            return null;
        }

        if ($user->status !== 'active') {
            return null; // banned / inactive / pending_verification — surfaced by controller with specific message
        }

        $db->table('login_history')->insert([
            'user_id'    => $user->id,
            'ip_address' => $ip,
            'device'     => $device,
            'browser'    => $browser,
            'status'     => 'success',
            'created_at' => date('Y-m-d H:i:s'),
        ]);

        $this->userModel->update($user->id, ['last_login_at' => date('Y-m-d H:i:s')]);
        $this->activity->log($user->id, $user->agency_id, $user->role_slug, 'login', ['ip' => $ip]);

        return $user;
    }

    public function issueTokenPair(User $user): array
    {
        return [
            'access_token'  => $this->jwt->issueAccessToken($user->id, $user->role_slug),
            'refresh_token' => $this->jwt->issueRefreshToken($user->id),
            'expires_in'    => (int) (env('jwt.accessTokenTTL') ?? 900),
        ];
    }

    public function logout(int $userId, ?string $refreshToken = null): void
    {
        if ($refreshToken) {
            Database::connect()->table('api_tokens')
                ->where('refresh_token_hash', hash('sha256', $refreshToken))
                ->update(['revoked_at' => date('Y-m-d H:i:s')]);
        }
        $this->activity->log($userId, null, null, 'logout', []);
    }

    public function logoutAllDevices(int $userId): void
    {
        $this->jwt->revokeAllForUser($userId);
    }

    public function requestPasswordReset(string $email): void
    {
        $user = $this->userModel->where('email', $email)->first();
        if (! $user) {
            return; // never reveal whether an email exists
        }

        $token = bin2hex(random_bytes(32));
        Database::connect()->table('password_resets')->insert([
            'email'      => $email,
            'token'      => $token,
            'expires_at' => date('Y-m-d H:i:s', strtotime('+1 hour')),
            'created_at' => date('Y-m-d H:i:s'),
        ]);

        $resetUrl = base_url('reset-password/' . $token);
        (new \App\Libraries\NotificationChannels\EmailChannel())->send(
            $email, 'Reset your password', "Click here to reset your password: {$resetUrl} (expires in 1 hour)"
        );
    }

    public function resetPassword(string $token, string $newPassword): bool
    {
        $db = Database::connect();
        $reset = $db->table('password_resets')
            ->where('token', $token)->where('expires_at >', date('Y-m-d H:i:s'))
            ->get()->getRow();

        if (! $reset) {
            return false;
        }

        $user = $this->userModel->where('email', $reset->email)->first();
        if (! $user) {
            return false;
        }

        $this->userModel->update($user->id, ['password_hash' => password_hash($newPassword, PASSWORD_ARGON2ID)]);
        $db->table('password_resets')->where('email', $reset->email)->delete();
        $this->jwt->revokeAllForUser($user->id); // password change invalidates all sessions/tokens

        return true;
    }

    private function roleIdFor(string $slug): int
    {
        $row = Database::connect()->table('roles')->where('slug', $slug)->get()->getRow();
        if (! $row) {
            throw new \RuntimeException("Role '{$slug}' not seeded — run MasterSeeder.");
        }
        return (int) $row->id;
    }

    private function uniqueSlug(string $companyName): string
    {
        $base = url_title($companyName, '-', true);
        $slug = $base;
        $i = 1;
        while ($this->agencyModel->findBySlug($slug) !== null) {
            $slug = $base . '-' . (++$i);
        }
        return $slug;
    }
}
