<?php

namespace App\Services\Recruitment;

use App\Models\Applicant;
use App\Models\Interview;
use App\Models\JobPosting;
use App\Models\User;
use Carbon\Carbon;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;

class RecruitmentHomeService
{
    public function __construct(
        protected ReferralProgressService $progress,
        protected RecruitmentSlaService $sla
    ) {}

    /**
     * @return array<string, mixed>
     */
    public function build(int $companyId, Carbon $now): array
    {
        $monthStart = $now->copy()->startOfMonth()->startOfDay();
        $monthEnd = $now->copy()->endOfDay();
        $today = $now->toDateString();

        $activeJobPostings = JobPosting::query()->where('status', 'active')->count();

        $applicantsMtd = $this->countApplicantsInRange($monthStart, $monthEnd);
        $referralsMtd = $this->countApplicantsInRange($monthStart, $monthEnd, referralOnly: true);

        $offersMtd = Applicant::query()
            ->where('stage', 'offer')
            ->whereBetween('updated_at', [$monthStart, $monthEnd])
            ->count();

        $pipeline = Applicant::query()
            ->selectRaw('stage, COUNT(*) as total')
            ->groupBy('stage')
            ->pluck('total', 'stage')
            ->toArray();

        $pipelineKeys = ['applied', 'screening', 'interview', 'offer', 'rejected'];
        $pipelineLabels = ['Applied', 'Screening', 'Interview', 'Offer', 'Rejected'];
        $pipelineCounts = array_map(fn (string $key): int => (int) ($pipeline[$key] ?? 0), $pipelineKeys);

        $interviewsToday = Interview::query()
            ->whereDate('scheduled_at', $today)
            ->where('status', 'scheduled')
            ->count();

        $upcomingInterviewCount = Interview::query()
            ->where('status', 'scheduled')
            ->whereBetween('scheduled_at', [$now->copy()->startOfDay(), $now->copy()->addDays(7)->endOfDay()])
            ->count();

        $completedInterviewsMtd = Interview::query()
            ->where('status', 'completed')
            ->whereBetween('scheduled_at', [$monthStart, $monthEnd])
            ->count();

        $followUpsOverdue = Interview::query()
            ->where('status', 'scheduled')
            ->whereNotNull('follow_up_at')
            ->where('follow_up_at', '<=', $now)
            ->count();

        $newReferralsWeek = Applicant::query()
            ->where('source', 'referral')
            ->where('created_at', '>=', $now->copy()->subDays(7))
            ->count();

        $stuckScreening = Applicant::query()
            ->where('stage', 'screening')
            ->where('updated_at', '<=', $now->copy()->subDays(7))
            ->count();

        $slaOverdue = Applicant::query()
            ->whereIn('stage', ['applied', 'screening'])
            ->whereNotNull('sla_due_at')
            ->where('sla_due_at', '<=', $now)
            ->count();

        return [
            'greeting' => 'Recruitment operations',
            'date_label' => $now->format('l, j F Y'),
            'kpis' => [
                [
                    'title' => 'Active jobs',
                    'value' => $activeJobPostings,
                    'hint' => 'Published openings',
                    'href' => route('job-postings.index', ['status' => 'active']),
                    'tone' => 'sky',
                ],
                [
                    'title' => 'Open pipeline',
                    'value' => array_sum(array_map(fn (string $k): int => (int) ($pipeline[$k] ?? 0), ['applied', 'screening', 'interview', 'offer'])),
                    'hint' => $applicantsMtd.' new this month',
                    'href' => route('applicants.index'),
                    'tone' => 'emerald',
                ],
                [
                    'title' => 'Referrals (MTD)',
                    'value' => $referralsMtd,
                    'hint' => $newReferralsWeek.' in last 7 days',
                    'href' => route('applicants.index', ['source' => 'referral']),
                    'tone' => 'amber',
                ],
                [
                    'title' => 'Interviews today',
                    'value' => $interviewsToday,
                    'hint' => $upcomingInterviewCount.' in next 7 days',
                    'href' => route('interviews.index'),
                    'tone' => 'indigo',
                ],
                [
                    'title' => 'Offers (MTD)',
                    'value' => $offersMtd,
                    'hint' => 'Candidates in offer stage',
                    'href' => route('applicants.index', ['stage' => 'offer']),
                    'tone' => 'emerald',
                ],
                [
                    'title' => 'Overdue follow-ups',
                    'value' => $followUpsOverdue,
                    'hint' => 'Needs HR action',
                    'href' => route('interviews.index'),
                    'tone' => $followUpsOverdue > 0 ? 'rose' : 'slate',
                ],
            ],
            'secondary_stats' => [
                ['label' => 'Completed interviews (MTD)', 'value' => $completedInterviewsMtd],
                ['label' => 'SLA overdue', 'value' => $slaOverdue],
            ],
            'action_queue' => $this->actionQueue($now, $newReferralsWeek, $stuckScreening, $followUpsOverdue, $slaOverdue),
            'quick_actions' => $this->quickActions(),
            'referral_leaderboard' => $this->referralLeaderboard($monthStart, $monthEnd),
            'chartPipeline' => ['labels' => $pipelineLabels, 'data' => $pipelineCounts],
            'chartApplicantsTrend' => $this->applicantsLastSixMonthsSeries($now),
            'chartReferralSplit' => $this->referralSplitSeries(),
            'chartInterviewThroughput' => $this->interviewWeeklySeries($now),
            'recentActivity' => $this->buildRecentActivity(),
            'followUpPreview' => Interview::query()
                ->with(['applicant:id,name', 'applicant.jobPosting:id,title'])
                ->where('status', 'scheduled')
                ->whereNotNull('follow_up_at')
                ->orderBy('follow_up_at')
                ->limit(8)
                ->get(),
        ];
    }

    /**
     * @return array<string, mixed>
     */
    public function empty(): array
    {
        return [
            'greeting' => 'Recruitment operations',
            'date_label' => now()->format('l, j F Y'),
            'kpis' => [],
            'secondary_stats' => [],
            'action_queue' => [],
            'quick_actions' => [],
            'referral_leaderboard' => [],
            'chartPipeline' => ['labels' => [], 'data' => []],
            'chartApplicantsTrend' => ['labels' => [], 'applicants' => [], 'referrals' => []],
            'chartReferralSplit' => ['labels' => ['Referral', 'Direct'], 'data' => [0, 0]],
            'chartInterviewThroughput' => ['labels' => [], 'completed' => [], 'scheduled' => []],
            'recentActivity' => collect(),
            'followUpPreview' => collect(),
        ];
    }

    /**
     * @return list<array{key: string, title: string, count: int, href: string, rows: list<array<string, mixed>>}>
     */
    protected function actionQueue(Carbon $now, int $newReferralsWeek, int $stuckScreening, int $followUpsOverdue, int $slaOverdue = 0): array
    {
        $items = [];

        if ($slaOverdue > 0) {
            $items[] = [
                'key' => 'sla',
                'title' => 'SLA overdue (screening)',
                'count' => $slaOverdue,
                'href' => route('applicants.index', ['sla' => 'overdue']),
                'rows' => Applicant::query()
                    ->with('jobPosting:id,title')
                    ->whereIn('stage', ['applied', 'screening'])
                    ->whereNotNull('sla_due_at')
                    ->where('sla_due_at', '<=', $now)
                    ->orderBy('sla_due_at')
                    ->limit(5)
                    ->get()
                    ->map(fn (Applicant $a) => [
                        'name' => $a->name,
                        'meta' => ($a->jobPosting?->title ?? 'Role').' · due '.$a->sla_due_at?->format('d M'),
                        'href' => route('applicants.show', $a),
                    ])
                    ->values()
                    ->all(),
            ];
        }

        if ($newReferralsWeek > 0) {
            $rows = Applicant::query()
                ->with(['jobPosting:id,title', 'referredBy:id,name'])
                ->where('source', 'referral')
                ->where('created_at', '>=', $now->copy()->subDays(7))
                ->orderByDesc('created_at')
                ->limit(5)
                ->get()
                ->map(fn (Applicant $a) => [
                    'name' => $a->name,
                    'meta' => ($a->jobPosting?->title ?? 'Role').' · via '.($a->referredBy?->name ?? 'Staff'),
                    'href' => route('applicants.show', $a),
                ])
                ->values()
                ->all();

            $items[] = [
                'key' => 'referrals',
                'title' => 'New employee referrals',
                'count' => $newReferralsWeek,
                'href' => route('applicants.index', ['source' => 'referral']),
                'rows' => $rows,
            ];
        }

        if ($stuckScreening > 0) {
            $items[] = [
                'key' => 'screening',
                'title' => 'Stuck in screening',
                'count' => $stuckScreening,
                'href' => route('applicants.index', ['stage' => 'screening']),
                'rows' => [],
            ];
        }

        if ($followUpsOverdue > 0) {
            $items[] = [
                'key' => 'followups',
                'title' => 'Overdue interview follow-ups',
                'count' => $followUpsOverdue,
                'href' => route('interviews.index'),
                'rows' => [],
            ];
        }

        return $items;
    }

    /**
     * @return list<array{user_id: int, name: string, referrals: int, offers: int, bonus_pending: float}>
     */
    public function referralLeaderboard(Carbon $monthStart, Carbon $monthEnd): array
    {
        $rows = Applicant::query()
            ->select('referred_by_user_id', DB::raw('COUNT(*) as referrals'), DB::raw("SUM(CASE WHEN stage = 'offer' THEN 1 ELSE 0 END) as offers"))
            ->where('source', 'referral')
            ->whereNotNull('referred_by_user_id')
            ->where(function ($q) use ($monthStart, $monthEnd) {
                $q->whereBetween('applied_at', [$monthStart->toDateString(), $monthEnd->toDateString()])
                    ->orWhereBetween('created_at', [$monthStart, $monthEnd]);
            })
            ->groupBy('referred_by_user_id')
            ->orderByDesc('referrals')
            ->limit(10)
            ->get();

        $userIds = $rows->pluck('referred_by_user_id')->filter()->all();
        $users = User::query()->whereIn('id', $userIds)->get(['id', 'name'])->keyBy('id');

        $bonusPending = Applicant::query()
            ->select('referred_by_user_id', DB::raw('SUM(referral_bonus_amount) as total'))
            ->where('referral_bonus_status', 'pending')
            ->whereNotNull('referred_by_user_id')
            ->groupBy('referred_by_user_id')
            ->pluck('total', 'referred_by_user_id');

        return $rows->map(function ($row) use ($users, $bonusPending) {
            $userId = (int) $row->referred_by_user_id;

            return [
                'user_id' => $userId,
                'name' => $users[$userId]->name ?? 'Employee',
                'referrals' => (int) $row->referrals,
                'offers' => (int) $row->offers,
                'bonus_pending' => (float) ($bonusPending[$userId] ?? 0),
            ];
        })->values()->all();
    }

    /**
     * @return array<string, Collection<int, Applicant>>
     */
    public function pipelineBoardGroups(): array
    {
        $stages = ['applied', 'screening', 'interview', 'offer', 'rejected'];
        $groups = [];

        foreach ($stages as $stage) {
            $groups[$stage] = Applicant::query()
                ->with(['jobPosting:id,title', 'referredBy:id,name'])
                ->where('stage', $stage)
                ->orderByDesc('updated_at')
                ->limit(40)
                ->get();
        }

        return $groups;
    }

    /**
     * @return list<array{label: string, href: string, icon: string, primary: bool}>
     */
    protected function quickActions(): array
    {
        return [
            ['label' => 'Create job', 'href' => route('job-postings.create'), 'icon' => 'bi-briefcase', 'tone' => 'teal', 'primary' => true],
            ['label' => 'Add applicant', 'href' => route('applicants.create'), 'icon' => 'bi-person-plus', 'tone' => 'sky', 'primary' => false],
            ['label' => 'View referrals', 'href' => route('applicants.index', ['source' => 'referral']), 'icon' => 'bi-diagram-3', 'tone' => 'violet', 'primary' => false],
            ['label' => 'Board view', 'href' => route('applicants.index', ['view' => 'board']), 'icon' => 'bi-kanban', 'tone' => 'amber', 'primary' => false],
        ];
    }

    protected function countApplicantsInRange(Carbon $start, Carbon $end, bool $referralOnly = false): int
    {
        return Applicant::query()
            ->when($referralOnly, fn ($q) => $q->where('source', 'referral'))
            ->where(function ($q) use ($start, $end) {
                $q->whereBetween('applied_at', [$start->toDateString(), $end->toDateString()])
                    ->orWhere(function ($q2) use ($start, $end) {
                        $q2->whereNull('applied_at')
                            ->whereBetween('created_at', [$start, $end]);
                    });
            })
            ->count();
    }

    /**
     * @return array{labels: array<int, string>, applicants: array<int, int>, referrals: array<int, int>}
     */
    protected function applicantsLastSixMonthsSeries(Carbon $now): array
    {
        $labels = [];
        $applicants = [];
        $referrals = [];

        for ($i = 5; $i >= 0; $i--) {
            $d = $now->copy()->subMonths($i);
            $labels[] = $d->format('M Y');
            $applicants[] = (int) Applicant::query()
                ->where(function ($q) use ($d) {
                    $q->whereYear('applied_at', $d->year)->whereMonth('applied_at', $d->month)
                        ->orWhere(function ($q2) use ($d) {
                            $q2->whereNull('applied_at')
                                ->whereYear('created_at', $d->year)
                                ->whereMonth('created_at', $d->month);
                        });
                })
                ->count();
            $referrals[] = (int) Applicant::query()
                ->where('source', 'referral')
                ->where(function ($q) use ($d) {
                    $q->whereYear('applied_at', $d->year)->whereMonth('applied_at', $d->month)
                        ->orWhere(function ($q2) use ($d) {
                            $q2->whereNull('applied_at')
                                ->whereYear('created_at', $d->year)
                                ->whereMonth('created_at', $d->month);
                        });
                })
                ->count();
        }

        return compact('labels', 'applicants', 'referrals');
    }

    /**
     * @return array{labels: array<int, string>, data: array<int, int>}
     */
    protected function referralSplitSeries(): array
    {
        $referral = Applicant::query()->where('source', 'referral')->count();
        $direct = Applicant::query()
            ->where(function ($q) {
                $q->where('source', '!=', 'referral')->orWhereNull('source');
            })
            ->count();

        return [
            'labels' => ['Employee referral', 'Direct / HR'],
            'data' => [$referral, $direct],
        ];
    }

    /**
     * @return array{labels: array<int, string>, completed: array<int, int>, scheduled: array<int, int>}
     */
    protected function interviewWeeklySeries(Carbon $now): array
    {
        $labels = [];
        $completed = [];
        $scheduled = [];

        for ($i = 5; $i >= 0; $i--) {
            $weekStart = $now->copy()->subWeeks($i)->startOfWeek();
            $weekEnd = $weekStart->copy()->endOfWeek();
            $labels[] = $weekStart->format('M j');
            $completed[] = (int) Interview::query()
                ->where('status', 'completed')
                ->whereBetween('scheduled_at', [$weekStart, $weekEnd])
                ->count();
            $scheduled[] = (int) Interview::query()
                ->where('status', 'scheduled')
                ->whereBetween('scheduled_at', [$weekStart, $weekEnd])
                ->count();
        }

        return compact('labels', 'completed', 'scheduled');
    }

    protected function buildRecentActivity(): Collection
    {
        $applicants = Applicant::query()
            ->with(['jobPosting:id,title', 'referredBy:id,name'])
            ->orderByDesc('created_at')
            ->limit(10)
            ->get()
            ->map(function (Applicant $a) {
                return [
                    'type' => $a->source === 'referral' ? 'Referral' : 'Applicant',
                    'label' => $a->name,
                    'detail' => $a->jobPosting?->title ?? '—',
                    'meta' => ucfirst($a->stage).($a->referredBy ? ' · '.$a->referredBy->name : ''),
                    'at' => $a->applied_at?->format('Y-m-d') ?? $a->created_at?->format('Y-m-d'),
                    'sort' => $a->applied_at?->toDateString() ?? $a->created_at?->toDateTimeString(),
                    'url' => route('applicants.show', $a),
                ];
            });

        $interviews = Interview::query()
            ->with(['applicant:id,name', 'applicant.jobPosting:id,title'])
            ->orderByDesc('scheduled_at')
            ->limit(10)
            ->get()
            ->map(function (Interview $i) {
                return [
                    'type' => 'Interview',
                    'label' => $i->applicant?->name ?? 'Applicant',
                    'detail' => $i->applicant?->jobPosting?->title ?? '—',
                    'meta' => ucfirst($i->status),
                    'at' => $i->scheduled_at?->format('Y-m-d H:i') ?? '—',
                    'sort' => $i->scheduled_at?->toDateTimeString() ?? '',
                    'url' => route('interviews.edit', $i),
                ];
            });

        return $applicants->concat($interviews)->sortByDesc('sort')->values()->take(18);
    }

    /**
     * @return array<string, int>
     */
    public function jobPipelineCounts(JobPosting $job): array
    {
        $counts = Applicant::query()
            ->where('job_posting_id', $job->id)
            ->selectRaw('stage, COUNT(*) as total')
            ->groupBy('stage')
            ->pluck('total', 'stage')
            ->toArray();

        return [
            'applied' => (int) ($counts['applied'] ?? 0),
            'screening' => (int) ($counts['screening'] ?? 0),
            'interview' => (int) ($counts['interview'] ?? 0),
            'offer' => (int) ($counts['offer'] ?? 0),
            'rejected' => (int) ($counts['rejected'] ?? 0),
        ];
    }
}
