<?php

namespace App\Services\Interview;

use App\Libraries\VideoMeeting\GoogleMeetAdapter;
use App\Libraries\VideoMeeting\ZoomMeetingAdapter;
use App\Models\CandidateProfileModel;
use App\Models\InterviewFileModel;
use App\Models\InterviewModel;
use App\Models\InterviewUpdateModel;
use App\Models\JobApplicationModel;
use App\Models\JobModel;
use App\Services\Notification\NotificationService;
use App\Services\Support\ActivityLogger;
use App\Services\Support\AuditService;

class InterviewService
{
    private InterviewModel $interviews;
    private InterviewUpdateModel $updates;
    private InterviewFileModel $files;
    private JobApplicationModel $applications;
    private JobModel $jobModel;
    private CandidateProfileModel $candidateProfiles;
    private NotificationService $notifications;
    private ActivityLogger $activity;
    private AuditService $audit;

    public function __construct()
    {
        $this->interviews        = new InterviewModel();
        $this->updates           = new InterviewUpdateModel();
        $this->files             = new InterviewFileModel();
        $this->applications      = new JobApplicationModel();
        $this->jobModel          = new JobModel();
        $this->candidateProfiles = new CandidateProfileModel();
        $this->notifications     = new NotificationService();
        $this->activity          = new ActivityLogger();
        $this->audit             = new AuditService();
    }

    public function schedule(int $jobApplicationId, int $scheduledByUserId, string $mode, string $scheduledAt, int $durationMinutes, ?string $location = null): array
    {
        $application = $this->applications->find($jobApplicationId);
        if (! $application) {
            throw new \RuntimeException('Application not found.');
        }
        if (! $application->canTransitionTo('interview_scheduled') && $application->status !== 'interview_scheduled') {
            throw new \InvalidArgumentException("Cannot schedule an interview from application status '{$application->status}'.");
        }

        $meetingLink = null;
        $externalMeetingId = null;

        if (in_array($mode, ['zoom', 'google_meet'], true)) {
            $job = $this->jobModel->find($application->job_id);
            $adapter = $mode === 'zoom' ? new ZoomMeetingAdapter() : new GoogleMeetAdapter();

            $meeting = $adapter->createMeeting("Interview: {$job->title}", date('c', strtotime($scheduledAt)), $durationMinutes);
            $meetingLink = $meeting['join_url'];
            $externalMeetingId = $meeting['external_meeting_id'];
        }

        $interviewId = $this->interviews->insert([
            'job_application_id'   => $jobApplicationId,
            'scheduled_by_user_id' => $scheduledByUserId,
            'scheduled_at'         => $scheduledAt,
            'duration_minutes'     => $durationMinutes,
            'mode'                 => $mode,
            'meeting_link'         => $meetingLink,
            'location'             => $mode === 'onsite' ? $location : null,
            'status'               => 'scheduled',
        ], true);

        if ($application->status !== 'interview_scheduled') {
            $this->applications->update($jobApplicationId, ['status' => 'interview_scheduled']);
        }

        $this->notifyCandidate($application, $jobApplicationId, $scheduledAt);
        $this->activity->log($scheduledByUserId, null, null, 'interview_schedule', ['interview_id' => $interviewId]);
        $this->logUpdate($interviewId, $scheduledByUserId, 'status_change', "Interview scheduled for " . date('M j, Y g:i A', strtotime($scheduledAt)) . " via " . str_replace('_', ' ', $mode) . ".");

        return $this->interviews->find($interviewId);
    }

    public function reschedule(int $interviewId, string $newScheduledAt, int $changedByUserId): array
    {
        $interview = $this->interviews->find($interviewId);
        if (! $interview) {
            throw new \RuntimeException('Interview not found.');
        }

        $this->interviews->update($interviewId, ['scheduled_at' => $newScheduledAt, 'status' => 'rescheduled']);
        $this->audit->record($changedByUserId, 'interviews', 'reschedule', $interviewId, ['scheduled_at' => $interview['scheduled_at']], ['scheduled_at' => $newScheduledAt]);

        $application = $this->applications->find($interview['job_application_id']);
        $this->notifyCandidate($application, $interview['job_application_id'], $newScheduledAt, 'rescheduled');
        $this->logUpdate($interviewId, $changedByUserId, 'status_change', "Rescheduled from " . date('M j, Y g:i A', strtotime($interview['scheduled_at'])) . " to " . date('M j, Y g:i A', strtotime($newScheduledAt)) . ".");

        return $this->interviews->find($interviewId);
    }

    public function cancel(int $interviewId, int $cancelledByUserId): void
    {
        $interview = $this->interviews->find($interviewId);
        if (! $interview) {
            throw new \RuntimeException('Interview not found.');
        }

        if ($interview['mode'] === 'zoom' && ! empty($interview['meeting_link'])) {
            // external_meeting_id isn't stored separately in this schema
            // (meeting_link is derived from it) — a production build would
            // add an external_meeting_id column to avoid re-parsing the URL.
        }

        $this->interviews->update($interviewId, ['status' => 'cancelled']);
        $this->activity->log($cancelledByUserId, null, null, 'interview_cancel', ['interview_id' => $interviewId]);
        $this->logUpdate($interviewId, $cancelledByUserId, 'status_change', 'Interview cancelled.');
    }

    public function submitFeedback(int $interviewId, string $feedback, int $rating, int $submittedByUserId): array
    {
        if ($rating < 1 || $rating > 5) {
            throw new \InvalidArgumentException('Rating must be between 1 and 5.');
        }

        $this->interviews->update($interviewId, [
            'feedback'        => $feedback,
            'feedback_rating' => $rating,
            'status'          => 'completed',
        ]);

        $interview = $this->interviews->find($interviewId);
        $this->applications->update($interview['job_application_id'], ['status' => 'interview_completed']);

        $this->audit->record($submittedByUserId, 'interviews', 'feedback', $interviewId, null, ['rating' => $rating]);
        $this->logUpdate($interviewId, $submittedByUserId, 'status_change', "Feedback submitted — rated {$rating}/5.");

        return $interview;
    }

    /**
     * A manual note from an agency owner or recruiter — the CRM-style
     * "add an update" action, distinct from the auto-logged status-change
     * entries above.
     */
    public function addNote(int $interviewId, int $userId, string $content): array
    {
        if (! $this->interviews->find($interviewId)) {
            throw new \RuntimeException('Interview not found.');
        }
        $id = $this->logUpdate($interviewId, $userId, 'note', $content);
        return $this->updates->find($id);
    }

    /**
     * Attaches a file to the interview (assessment doc, signed offer,
     * interview notes, etc.) and logs a matching timeline entry so the
     * file's appearance shows up in context, not just in a separate list.
     */
    public function addFile(int $interviewId, int $uploadedByUserId, string $filePath, string $originalFilename, ?int $fileSizeKb = null): array
    {
        if (! $this->interviews->find($interviewId)) {
            throw new \RuntimeException('Interview not found.');
        }

        $fileId = $this->files->insert([
            'interview_id'        => $interviewId,
            'uploaded_by_user_id' => $uploadedByUserId,
            'file_path'           => $filePath,
            'original_filename'   => $originalFilename,
            'file_size_kb'        => $fileSizeKb,
            'created_at'          => date('Y-m-d H:i:s'),
        ], true);

        $this->logUpdate($interviewId, $uploadedByUserId, 'file_added', "Attached file: {$originalFilename}");

        return $this->files->find($fileId);
    }

    public function listFiles(int $interviewId): array
    {
        return $this->files->where('interview_id', $interviewId)->orderBy('created_at', 'DESC')->findAll();
    }

    /**
     * The full CRM-style activity timeline for one interview — manual
     * notes, auto-logged status changes, and file-added markers, newest
     * first. Visible to the whole agency team (owner + recruiters) and
     * the candidate (read-only, see access checks in the controller).
     */
    public function timeline(int $interviewId): array
    {
        $db = \Config\Database::connect();
        return $db->table('interview_updates iu')
            ->select('iu.*, u.name as posted_by_name')
            ->join('users u', 'u.id = iu.posted_by_user_id', 'left')
            ->where('iu.interview_id', $interviewId)
            ->orderBy('iu.created_at', 'DESC')
            ->get()->getResultArray();
    }

    private function logUpdate(int $interviewId, ?int $userId, string $type, string $content): int
    {
        return $this->updates->insert([
            'interview_id'      => $interviewId,
            'posted_by_user_id' => $userId,
            'update_type'       => $type,
            'content'           => $content,
            'created_at'        => date('Y-m-d H:i:s'),
        ], true);
    }

    public function forApplication(int $applicationId): array
    {
        return $this->interviews->forApplication($applicationId);
    }

    public function upcomingForCandidate(int $candidateUserId): array
    {
        return $this->interviews->upcomingForCandidate($candidateUserId);
    }

    /**
     * Cron entry point — sends reminders for interviews starting within
     * the next hour. Idempotent-ish: relies on the cron running every 30
     * min per the deployment guide, so double-sends are bounded.
     */
    public function sendReminders(): int
    {
        $upcoming = $this->interviews->needingReminders(60);
        foreach ($upcoming as $interview) {
            $application = $this->applications->find($interview['job_application_id']);
            $this->notifyCandidate($application, $interview['job_application_id'], $interview['scheduled_at'], 'reminder');
        }
        return count($upcoming);
    }

    private function notifyCandidate($application, int $jobApplicationId, string $scheduledAt, string $kind = 'invite'): void
    {
        $candidateProfile = $this->candidateProfiles->find($application->candidate_profile_id);
        $job = $this->jobModel->find($application->job_id);

        $triggerKey = $kind === 'reminder' ? 'interview_reminder' : 'interview_invite';

        $this->notifications->send($candidateProfile->user_id, $triggerKey, [
            'job_title'    => $job->title,
            'scheduled_at' => $scheduledAt,
        ]);
    }
}
