<?php

namespace App\Http\Controllers;

use App\Models\ImportedJob;
use App\Models\JobInterest;
use App\Models\JobSource;
use App\Services\JobMatchingService;
use Illuminate\Contracts\View\View;
use Illuminate\Http\Request;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Gate;
use Illuminate\Validation\Rule;

class JobController extends Controller
{
    public function index(Request $request, JobMatchingService $matchingService): View
    {
        Gate::authorize('viewAny', ImportedJob::class);

        $filters = $request->validate([
            'category' => ['nullable', 'string', 'max:80'],
            'employment_type' => ['nullable', 'string', 'max:80'],
            'experience_level' => ['nullable', 'string', 'max:80'],
            'location_type' => ['nullable', 'string', 'max:80'],
            'source_id' => ['nullable', 'integer', 'exists:job_sources,id'],
            'salary_min' => ['nullable', 'integer', 'min:0', 'max:10000000'],
            'match_min' => ['nullable', 'integer', 'min:0', 'max:100'],
            'posted_within' => ['nullable', 'integer', Rule::in([7, 14, 30, 60])],
            'saved' => ['nullable', 'boolean'],
            'applied' => ['nullable', 'boolean'],
            'application_status' => ['nullable', 'string', 'max:40'],
            'search' => ['nullable', 'string', 'max:120'],
        ]);

        $user = $request->user();
        $savedJobIds = $user
            ->savedJobs()
            ->pluck('imported_job_id')
            ->all();
        $applicationsByJobId = $user
            ->applications()
            ->with('importedJob')
            ->latest('applied_at')
            ->get()
            ->keyBy('imported_job_id');

        $rankedJobs = $matchingService->rankedJobsFor($request->user());

        $filteredJobs = $rankedJobs
            ->filter(fn (ImportedJob $job): bool => $this->matchesFilters($job, $filters, $savedJobIds, $applicationsByJobId))
            ->values();
        $jobs = $this->paginateJobs($filteredJobs, $request);

        return view('jobs.index', [
            'jobs' => $jobs,
            'filters' => $filters,
            'savedJobIds' => $savedJobIds,
            'applicationsByJobId' => $applicationsByJobId,
            'categoryOptions' => $this->distinctOptions('category'),
            'employmentOptions' => $this->distinctOptions('employment_type'),
            'experienceOptions' => $this->distinctOptions('experience_level'),
            'locationOptions' => $this->distinctOptions('location_type'),
            'sourceOptions' => JobSource::query()
                ->orderBy('name')
                ->get(['id', 'name']),
            'totalMatches' => $rankedJobs->count(),
            'filteredMatches' => $filteredJobs->count(),
        ]);
    }

    public function show(Request $request, ImportedJob $job, JobMatchingService $matchingService): View
    {
        Gate::authorize('view', $job);

        $job->loadMissing('source');
        $job->setAttribute('match_score', $matchingService->score($request->user(), $job));
        $request->user()->recordJobInterest($job, JobInterest::SOURCE_VIEWED);

        return view('jobs.show', [
            'job' => $job,
            'savedJobIds' => $request->user()->savedJobs()->pluck('imported_job_id')->all(),
            'application' => $request->user()
                ->applications()
                ->where('imported_job_id', $job->id)
                ->first(),
            'jobNote' => $request->user()
                ->jobNotes()
                ->where('imported_job_id', $job->id)
                ->first(),
            'aiAssistances' => $request->user()
                ->jobAiAssistances()
                ->where('imported_job_id', $job->id)
                ->get()
                ->keyBy('type'),
            'matchReasons' => $matchingService->explain($request->user(), $job),
        ]);
    }

    /**
     * @param  array<string, mixed>  $filters
     * @param  array<int, int>  $savedJobIds
     * @param  Collection<int, \App\Models\Application>  $applicationsByJobId
     */
    private function matchesFilters(ImportedJob $job, array $filters, array $savedJobIds, Collection $applicationsByJobId): bool
    {
        if (($filters['category'] ?? null) && $job->category !== $filters['category']) {
            return false;
        }

        if (($filters['employment_type'] ?? null) && $job->employment_type !== $filters['employment_type']) {
            return false;
        }

        if (($filters['experience_level'] ?? null) && $job->experience_level !== $filters['experience_level']) {
            return false;
        }

        if (($filters['location_type'] ?? null) && $job->location_type !== $filters['location_type']) {
            return false;
        }

        if (($filters['source_id'] ?? null) && (int) $job->job_source_id !== (int) $filters['source_id']) {
            return false;
        }

        if (($filters['salary_min'] ?? null) && ($job->salary_max ?? $job->salary_min ?? 0) < (int) $filters['salary_min']) {
            return false;
        }

        if (($filters['match_min'] ?? null) && (int) $job->match_score < (int) $filters['match_min']) {
            return false;
        }

        if (($filters['posted_within'] ?? null) && (! $job->posted_at || $job->posted_at->lt(now()->subDays((int) $filters['posted_within'])))) {
            return false;
        }

        if (($filters['saved'] ?? null) && ! in_array($job->id, $savedJobIds, true)) {
            return false;
        }

        if (($filters['applied'] ?? null) && ! $applicationsByJobId->has($job->id)) {
            return false;
        }

        if (($filters['application_status'] ?? null) && $applicationsByJobId->get($job->id)?->status !== $filters['application_status']) {
            return false;
        }

        if ($search = $filters['search'] ?? null) {
            $haystack = strtolower($job->title.' '.$job->company.' '.$job->summary.' '.implode(' ', $job->skills ?? []).' '.implode(' ', $job->tags ?? []));

            return str_contains($haystack, strtolower($search));
        }

        return true;
    }

    /**
     * @return array<int, string>
     */
    private function distinctOptions(string $column): array
    {
        return ImportedJob::query()
            ->active()
            ->whereNotNull($column)
            ->distinct()
            ->orderBy($column)
            ->pluck($column)
            ->filter()
            ->values()
            ->all();
    }

    /**
     * @param  Collection<int, ImportedJob>  $jobs
     * @return LengthAwarePaginator<int, ImportedJob>
     */
    private function paginateJobs(Collection $jobs, Request $request): LengthAwarePaginator
    {
        $perPage = 10;
        $page = max(1, (int) $request->query('page', 1));
        $total = $jobs->count();
        $maxPage = max(1, (int) ceil($total / $perPage));
        $page = min($page, $maxPage);

        return new LengthAwarePaginator(
            $jobs->slice(($page - 1) * $perPage, $perPage)->values(),
            $total,
            $perPage,
            $page,
            [
                'path' => $request->url(),
                'query' => collect($request->query())->except('page')->all(),
            ],
        );
    }
}
