<?php

namespace App\Services\Application;

use App\Models\ApplicationNoteModel;
use App\Models\ApplicationStatusHistoryModel;
use App\Models\CandidateProfileModel;
use App\Models\JobApplicationModel;
use App\Models\JobModel;
use App\Models\ResumeModel;
use App\Services\Notification\NotificationService;
use App\Services\Support\ActivityLogger;
use App\Services\Support\AuditService;

/**
 * Owns the application lifecycle: submission, ATS status transitions
 * (enforced against JobApplication::VALID_TRANSITIONS — see that entity),
 * notes, and recruiter assignment.
 */
class ApplicationService
{
    private JobApplicationModel $applications;
    private ApplicationStatusHistoryModel $history;
    private ApplicationNoteModel $notes;
    private JobModel $jobModel;
    private ResumeModel $resumeModel;
    private CandidateProfileModel $candidateProfiles;
    private NotificationService $notifications;
    private ActivityLogger $activity;
    private AuditService $audit;

    public function __construct()
    {
        $this->applications      = new JobApplicationModel();
        $this->history           = new ApplicationStatusHistoryModel();
        $this->notes             = new ApplicationNoteModel();
        $this->jobModel          = new JobModel();
        $this->resumeModel       = new ResumeModel();
        $this->candidateProfiles = new CandidateProfileModel();
        $this->notifications     = new NotificationService();
        $this->activity          = new ActivityLogger();
        $this->audit             = new AuditService();
    }

    public function apply(int $candidateProfileId, int $jobId, ?string $coverNote, ?int $resumeId): array
    {
        $job = $this->jobModel->find($jobId);
        if (! $job || ! $job->isPublished()) {
            throw new \RuntimeException('This job is not currently accepting applications.');
        }

        if ($this->applications->alreadyApplied($jobId, $candidateProfileId)) {
            throw new \RuntimeException('You have already applied to this job.');
        }

        if ($resumeId !== null) {
            $resume = $this->resumeModel->find($resumeId);
            if (! $resume || (int) $resume['candidate_profile_id'] !== $candidateProfileId) {
                throw new \InvalidArgumentException('Invalid resume selected.');
            }
        } else {
            $primary = $this->resumeModel->primaryFor($candidateProfileId);
            $resumeId = $primary['id'] ?? null;
        }

        // A resume is mandatory on every application — the agency must
        // always receive a CV, never an application with nothing attached.
        if ($resumeId === null) {
            throw new \RuntimeException('Please upload your resume before applying — go to My Profile to add one.');
        }

        $id = $this->applications->insert([
            'job_id'               => $jobId,
            'candidate_profile_id' => $candidateProfileId,
            'resume_id'            => $resumeId,
            'cover_note'           => $coverNote,
            'status'               => 'applied',
        ], true);

        $candidateProfile = $this->candidateProfiles->find($candidateProfileId);

        $this->history->insert([
            'job_application_id' => $id,
            'from_status'        => null,
            'to_status'          => 'applied',
            'changed_by_user_id' => $candidateProfile->user_id,
            'created_at'         => date('Y-m-d H:i:s'),
        ]);

        $this->activity->log($candidateProfile->user_id, null, 'candidate', 'job_apply', ['job_id' => $jobId, 'application_id' => $id]);

        $this->notifications->send($job->posted_by_user_id, 'job_application', ['job_title' => $job->title]);

        return $this->applications->find($id)->toArray();
    }

    public function changeStatus(int $applicationId, string $newStatus, int $changedByUserId, ?string $note = null): array
    {
        $application = $this->applications->find($applicationId);
        if (! $application) {
            throw new \RuntimeException('Application not found.');
        }

        $validStatuses = ['applied', 'under_review', 'shortlisted', 'interview_scheduled', 'interview_completed', 'hired', 'rejected'];
        if (! in_array($newStatus, $validStatuses, true)) {
            throw new \InvalidArgumentException("'{$newStatus}' is not a recognized application status.");
        }

        $fromStatus = $application->status;

        $this->applications->update($applicationId, ['status' => $newStatus]);

        $this->history->insert([
            'job_application_id' => $applicationId,
            'from_status'        => $fromStatus,
            'to_status'          => $newStatus,
            'changed_by_user_id' => $changedByUserId,
            'note'               => $note,
            'created_at'         => date('Y-m-d H:i:s'),
        ]);

        $this->audit->record($changedByUserId, 'applications', 'status_change', $applicationId, ['status' => $fromStatus], ['status' => $newStatus]);
        $this->activity->log($changedByUserId, null, null, 'application_status_change', [
            'application_id' => $applicationId, 'from' => $fromStatus, 'to' => $newStatus,
        ]);

        $job = $this->jobModel->find($application->job_id);
        $candidateProfile = $this->candidateProfiles->find($application->candidate_profile_id);
        $this->notifications->send($candidateProfile->user_id, 'application_status_change', [
            'job_title' => $job->title, 'status' => str_replace('_', ' ', $newStatus),
        ]);

        return $this->applications->find($applicationId)->toArray();
    }

    public function assignRecruiter(int $applicationId, int $recruiterId, int $assignedByUserId): array
    {
        $application = $this->applications->find($applicationId);
        if (! $application) {
            throw new \RuntimeException('Application not found.');
        }

        $this->applications->update($applicationId, ['assigned_recruiter_id' => $recruiterId]);
        $this->audit->record($assignedByUserId, 'applications', 'assign_recruiter', $applicationId, null, ['recruiter_id' => $recruiterId]);

        return $this->applications->find($applicationId)->toArray();
    }

    public function addNote(int $applicationId, int $authorUserId, string $note): array
    {
        $id = $this->notes->insert([
            'job_application_id' => $applicationId,
            'author_user_id'     => $authorUserId,
            'note'               => $note,
            'created_at'         => date('Y-m-d H:i:s'),
        ], true);

        return $this->notes->find($id);
    }

    public function getApplicationDetail(int $applicationId): array
    {
        $application = $this->applications->find($applicationId);
        if (! $application) {
            throw new \RuntimeException('Application not found.');
        }

        return [
            'application' => $application->toArray(),
            'history'     => $this->history->forApplication($applicationId),
            'notes'       => $this->notes->forApplication($applicationId),
        ];
    }

    public function kanbanForAgency(int $agencyId, ?int $jobId = null): array
    {
        return $this->applications->kanbanFor($agencyId, $jobId);
    }

    public function candidateApplications(int $candidateProfileId): array
    {
        return $this->applications->forCandidate($candidateProfileId);
    }
}
