<?php

namespace App\Services\Agency;

use App\Models\RecruiterInvitationModel;
use App\Models\UserModel;
use App\Services\Support\ActivityLogger;
use App\Services\Support\AuditService;
use Config\Database;

/**
 * Owns the "Recruiter Management" and "Team Permissions" Agency features
 * from the original spec. An Agency Owner invites by email (no account
 * required yet); the invitee accepts via a tokenized link and a users row
 * is created at that point, scoped to the inviting agency.
 */
class RecruiterManagementService
{
    private RecruiterInvitationModel $invitations;
    private UserModel $userModel;
    private ActivityLogger $activity;
    private AuditService $audit;

    public function __construct()
    {
        $this->invitations = new RecruiterInvitationModel();
        $this->userModel   = new UserModel();
        $this->activity    = new ActivityLogger();
        $this->audit       = new AuditService();
    }

    public function invite(int $agencyId, int $invitedByUserId, string $email, string $roleSlug = 'recruiter'): array
    {
        $existingUser = $this->userModel->where('email', $email)->first();
        if ($existingUser) {
            throw new \InvalidArgumentException('A user with this email already exists on the platform.');
        }

        $pending = $this->invitations->where('agency_id', $agencyId)
            ->where('email', $email)->where('status', 'pending')->first();
        if ($pending) {
            throw new \InvalidArgumentException('An invitation is already pending for this email.');
        }

        $role = Database::connect()->table('roles')->where('slug', $roleSlug)->get()->getRow();
        if (! $role || ! in_array($roleSlug, ['recruiter', 'support_staff'], true)) {
            throw new \InvalidArgumentException('Invalid role for a team invitation.');
        }

        $token = bin2hex(random_bytes(32));

        $id = $this->invitations->insert([
            'agency_id'  => $agencyId,
            'invited_by' => $invitedByUserId,
            'email'      => $email,
            'role_id'    => $role->id,
            'token'      => $token,
            'status'     => 'pending',
            'expires_at' => date('Y-m-d H:i:s', strtotime('+7 days')),
            'created_at' => date('Y-m-d H:i:s'),
        ], true);

        $this->activity->log($invitedByUserId, $agencyId, 'agency_owner', 'recruiter_invited', ['email' => $email]);

        $agency = Database::connect()->table('agencies')->where('id', $agencyId)->get()->getRow();
        $invitationUrl = base_url('accept-invitation/' . $token);

        // Notification is sent to the email directly since the invitee has
        // no user_id yet (NotificationService::send() requires one) — a
        // direct EmailChannel call, matching what recruiter_invitation's
        // template expects.
        $template = Database::connect()->table('notification_templates')
            ->where('trigger_key', 'recruiter_invitation')->where('channel', 'email')->get()->getRow();
        if ($template) {
            $body = strtr($template->body, ['{agency_name}' => $agency->company_name ?? '', '{invitation_url}' => $invitationUrl]);
            (new \App\Libraries\NotificationChannels\EmailChannel())->send($email, $template->subject, $body);
        }

        return ['invitation_id' => $id, 'token' => $token];
    }

    public function acceptInvitation(string $token, string $name, string $password): array
    {
        $invitation = $this->invitations->findValidByToken($token);
        if (! $invitation) {
            throw new \RuntimeException('This invitation link is invalid or has expired.');
        }

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

        $userId = $this->userModel->insert([
            'agency_id'         => $invitation['agency_id'],
            'role_id'           => $invitation['role_id'],
            'name'              => $name,
            'email'             => $invitation['email'],
            'password_hash'     => password_hash($password, PASSWORD_ARGON2ID),
            'status'            => 'active',
            'email_verified_at' => date('Y-m-d H:i:s'),
        ], true);

        $this->invitations->update($invitation['id'], [
            'status'       => 'accepted',
            'accepted_at'  => date('Y-m-d H:i:s'),
        ]);

        $db->transComplete();

        $this->activity->log($userId, $invitation['agency_id'], 'recruiter', 'invitation_accepted', []);

        return ['user_id' => $userId, 'agency_id' => $invitation['agency_id']];
    }

    public function remove(int $agencyId, int $targetUserId, int $removedByUserId): void
    {
        $target = $this->userModel->find($targetUserId);
        if (! $target || (int) $target->agency_id !== $agencyId) {
            throw new \InvalidArgumentException('User not found in this agency.');
        }
        if ($target->role_slug === 'agency_owner') {
            throw new \InvalidArgumentException('The agency owner cannot be removed.');
        }

        $before = $target->toArray();
        $this->userModel->update($targetUserId, ['status' => 'inactive']);

        $this->audit->record($removedByUserId, 'recruiters', 'remove', $targetUserId, $before, ['status' => 'inactive']);
        $this->activity->log($removedByUserId, $agencyId, 'agency_owner', 'recruiter_removed', ['target_user_id' => $targetUserId]);
    }

    /**
     * Grants or revokes a specific permission for one recruiter, layered on
     * top of their role defaults (see PermissionService + 04-roles-permissions-matrix.md §4).
     */
    public function setPermissionOverride(int $agencyId, int $targetUserId, int $permissionId, string $effect, int $setByUserId): void
    {
        $target = $this->userModel->find($targetUserId);
        if (! $target || (int) $target->agency_id !== $agencyId) {
            throw new \InvalidArgumentException('User not found in this agency.');
        }
        if (! in_array($effect, ['grant', 'revoke'], true)) {
            throw new \InvalidArgumentException('Effect must be "grant" or "revoke".');
        }

        $db = Database::connect();
        $existing = $db->table('user_permission_overrides')
            ->where('user_id', $targetUserId)->where('permission_id', $permissionId)->get()->getRow();

        if ($existing) {
            $db->table('user_permission_overrides')->where('id', $existing->id)->update(['effect' => $effect]);
        } else {
            $db->table('user_permission_overrides')->insert([
                'user_id'       => $targetUserId,
                'permission_id' => $permissionId,
                'effect'        => $effect,
                'created_at'    => date('Y-m-d H:i:s'),
            ]);
        }

        $this->audit->record($setByUserId, 'recruiters', 'set_permissions', $targetUserId, null, ['permission_id' => $permissionId, 'effect' => $effect]);
    }

    public function listTeam(int $agencyId): array
    {
        return $this->userModel->select('users.*, roles.slug as role_slug, roles.name as role_name')
            ->join('roles', 'roles.id = users.role_id')
            ->where('users.agency_id', $agencyId)
            ->where('users.status !=', 'inactive')
            ->findAll();
    }
}
