from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.core.paginator import Paginator
from django.db.models import Case, Count, IntegerField, Q, Value, When
from django.http import Http404, JsonResponse
from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse
from django.views.decorators.http import require_http_methods, require_POST

from companies.industries import INDUSTRIES
from companies.models import Company
from .category_data import category_form_choices, numbered_category_label

from seekers.models import SeekerProfile

from .application_requirements import extract_apply_email
from .application_utils import attach_profile_cv, profile_cv_filename, seeker_has_profile_cv
from .forms import GuestJobApplicationForm, JobApplicationForm, JobFilterForm, validate_seeker_candidate_info
from .recommendations import recommended_jobs_for_profile
from .models import Job, JobApplication, JobCategory, SavedJob
from .notifications import (
    notify_employer_application_by_email,
    notify_employer_guest_application,
    notify_employer_new_application,
)
from .querysets import with_job_logo
from .status import annotate_open_first, open_jobs_q
from subscriptions.usage import (
    can_seeker_save_job,
    job_accepts_external_applications,
    job_accepts_pezajob_applications,
    job_application_delivery_method,
)


def _filter_jobs(queryset, form):
    q = form.cleaned_data.get('q')
    location = form.cleaned_data.get('location')
    job_types = form.cleaned_data.get('job_type')
    experience = form.cleaned_data.get('experience')
    category = form.cleaned_data.get('category')
    industry = form.cleaned_data.get('industry')
    sort = form.cleaned_data.get('sort') or 'newest'

    if q:
        queryset = queryset.filter(
            Q(title__icontains=q)
            | Q(company_name__icontains=q)
            | Q(description__icontains=q)
            | Q(location__icontains=q)
            | Q(category__name__icontains=q)
        )
    if location:
        queryset = queryset.filter(
            Q(location__icontains=location)
            | Q(location_city__icontains=location)
        )
    company = form.cleaned_data.get('company')
    if company:
        queryset = queryset.filter(company_name__icontains=company)
    if job_types:
        queryset = queryset.filter(job_type__in=job_types)
    if experience:
        queryset = queryset.filter(experience_level__in=experience)
    if category:
        queryset = queryset.filter(category__slug=category)
    if industry:
        queryset = queryset.filter(company_industry=industry)

    if sort == 'salary_high':
        queryset = queryset.order_by('_open_sort', '-salary_max', '-created_at')
    elif sort == 'salary_low':
        queryset = queryset.order_by('_open_sort', 'salary_min', '-created_at')
    elif sort == 'ongoing':
        queryset = queryset.annotate(
            _ongoing_sort=Case(
                When(deadline__isnull=True, then=Value(0)),
                default=Value(1),
                output_field=IntegerField(),
            )
        ).order_by('_open_sort', '_ongoing_sort', '-created_at')
    elif sort == 'active':
        queryset = queryset.annotate(
            _active_sort=Case(
                When(is_active=True, then=Value(0)),
                default=Value(1),
                output_field=IntegerField(),
            )
        ).order_by('_active_sort', '_open_sort', '-created_at')
    else:
        queryset = queryset.order_by('_open_sort', '-is_featured', '-created_at')

    return queryset


JOBS_PER_PAGE = 10


def category_list(request):
    """Every official job category with its number of open jobs."""
    categories = list(
        JobCategory.active().annotate(job_count=Count('jobs', filter=open_jobs_q('jobs')))
    )
    return render(request, 'jobs/categories.html', {
        'categories': categories,
        'total_open_jobs': sum(c.job_count for c in categories),
    })


def job_list(request):
    queryset = with_job_logo(annotate_open_first(Job.objects.all())).select_related('category')
    category_choices = category_form_choices()
    industry_choices = [(name, name) for name in INDUSTRIES]
    form = JobFilterForm(
        request.GET or None,
        category_choices=category_choices,
        industry_choices=industry_choices,
    )

    if form.is_valid():
        queryset = _filter_jobs(queryset, form)
    else:
        queryset = queryset.order_by('_open_sort', '-is_featured', '-created_at')

    paginator = Paginator(queryset, JOBS_PER_PAGE)
    page_obj = paginator.get_page(request.GET.get('page'))

    saved_ids = set()
    if request.user.is_authenticated:
        saved_ids = set(
            SavedJob.objects.filter(
                user=request.user,
                job__in=page_obj.object_list,
            ).values_list('job_id', flat=True)
        )

    active_filter_labels = []
    if form.is_valid():
        if form.cleaned_data.get('category'):
            cat = JobCategory.objects.filter(slug=form.cleaned_data['category']).first()
            if cat:
                active_filter_labels.append(('category', numbered_category_label(cat)))
        if form.cleaned_data.get('industry'):
            active_filter_labels.append(('industry', form.cleaned_data['industry']))

    profile = getattr(request.user, 'seeker_profile', None) if request.user.is_authenticated else None
    recommended_jobs = []
    if profile and profile.job_category_id and not request.GET.get('category'):
        recommended_jobs = recommended_jobs_for_profile(profile, limit=4)

    query_params = request.GET.copy()
    query_params.pop('page', None)
    query_string = query_params.urlencode()

    page_range = paginator.get_elided_page_range(
        page_obj.number,
        on_each_side=1,
        on_ends=1,
    )

    context = {
        'jobs': page_obj,
        'page_obj': page_obj,
        'page_range': page_range,
        'form': form,
        'active_filter_labels': active_filter_labels,
        'recommended_jobs': recommended_jobs,
        'seeker_category': profile.job_category if profile and profile.job_category_id else None,
        'job_type_filters': [
            {
                'value': choice[0],
                'label': choice[1],
                'count': Job.objects.filter(open_jobs_q(), job_type=choice[0]).count(),
            }
            for choice in Job.JobType.choices
        ],
        'experience_filters': [
            {
                'value': choice[0],
                'label': choice[1],
                'count': Job.objects.filter(open_jobs_q(), experience_level=choice[0]).count(),
            }
            for choice in Job.ExperienceLevel.choices
        ],
        'total_count': paginator.count,
        'query_string': query_string,
        'saved_ids': saved_ids,
    }
    return render(request, 'jobs/list.html', context)


def _get_job_company(job):
    if job.company_slug:
        return Company.objects.filter(slug=job.company_slug, is_active=True).first()
    return None


def job_detail(request, slug):
    job = get_object_or_404(
        with_job_logo(Job.objects.select_related('category')).prefetch_related('attachments'),
        slug=slug,
    )
    job.views_count += 1
    job.save(update_fields=['views_count'])

    similar_jobs = (
        with_job_logo(annotate_open_first(Job.objects.filter(category=job.category)))
        .exclude(pk=job.pk)
        .order_by('_open_sort', '-created_at')[:4]
    )
    if similar_jobs.count() < 4:
        similar_jobs = with_job_logo(annotate_open_first(Job.objects.exclude(pk=job.pk))).order_by('_open_sort', '-created_at')[:4]

    is_saved = False
    has_applied = False
    seeker_profile = None
    if request.user.is_authenticated:
        is_saved = SavedJob.objects.filter(user=request.user, job=job).exists()
        has_applied = JobApplication.objects.filter(applicant=request.user, job=job).exists()
        if request.user.is_seeker:
            seeker_profile = SeekerProfile.objects.filter(user=request.user).first()

    application_form = JobApplicationForm(job=job, profile=seeker_profile)
    guest_application_form = GuestJobApplicationForm()

    company = _get_job_company(job)
    apply_via_pezajob = job_accepts_pezajob_applications(job, company)
    apply_via_external = job_accepts_external_applications(job)
    apply_requires_account = apply_via_pezajob and not apply_via_external

    context = {
        'job': job,
        'company': company,
        'similar_jobs': similar_jobs,
        'is_saved': is_saved,
        'has_applied': has_applied,
        'application_form': application_form,
        'guest_application_form': guest_application_form,
        'seeker_profile': seeker_profile,
        'profile_cv_attached': seeker_has_profile_cv(seeker_profile),
        'profile_cv_name': profile_cv_filename(seeker_profile),
        'apply_email': extract_apply_email(job.how_to_apply) or extract_apply_email(job.description),
        'apply_both_methods': apply_via_pezajob and apply_via_external,
        'apply_requires_account': apply_requires_account,
        'apply_via_pezajob': apply_via_pezajob,
        'apply_via_external': apply_via_external,
        'list_items': job.list_items,
        'display_content': job.display_content,
    }
    return render(request, 'jobs/detail.html', context)


@require_POST
def apply_to_job(request, slug):
    job = get_object_or_404(Job, slug=slug)
    if not job.accepts_applications:
        if (
            job.is_active
            and not job.is_expired
            and job.positions_remaining == 0
        ):
            messages.error(request, 'All positions for this job have been filled.')
        else:
            messages.error(request, 'This job is closed and no longer accepting applications.')
        return redirect('jobs:detail', slug=slug)

    if request.user.is_authenticated and not request.user.is_seeker:
        messages.error(request, 'Only job seeker accounts can apply for jobs.')
        return redirect('jobs:detail', slug=slug)

    company = _get_job_company(job)
    uses_pezajob = job_accepts_pezajob_applications(job, company)
    uses_external = job_accepts_external_applications(job)
    requires_account = uses_pezajob and not uses_external

    if not uses_pezajob and not uses_external:
        messages.error(request, 'This job is not accepting applications.')
        return redirect('jobs:detail', slug=slug)

    if requires_account and not request.user.is_authenticated:
        messages.info(
            request,
            'This employer accepts applications through PEZAJOB only - please log in or '
            'create a job seeker account to apply.',
        )
        return redirect(f"{reverse('accounts:login')}?next={request.path}")

    if request.user.is_authenticated:
        if JobApplication.objects.filter(job=job, applicant=request.user).exists():
            messages.info(request, 'You have already applied for this job.')
            return redirect('jobs:detail', slug=slug)

        seeker_profile = SeekerProfile.objects.filter(user=request.user).first()
        form = JobApplicationForm(
            request.POST,
            request.FILES,
            job=job,
            profile=seeker_profile,
        )
        if form.is_valid():
            for error in validate_seeker_candidate_info(job, request.user, seeker_profile):
                messages.error(request, error)
                return redirect('jobs:detail', slug=slug)

            application = form.save(commit=False)
            application.job = job
            application.applicant = request.user
            application.screening_answers = form.screening_answers()

            if (
                'cv' in job.application_documents_list()
                and 'cv_file' not in request.FILES
            ):
                attach_profile_cv(application, seeker_profile)

            if (
                'cv' in job.application_documents_list()
                and not application.cv_file
                and not seeker_has_profile_cv(seeker_profile)
            ):
                messages.error(request, 'Please upload your CV to apply for this job.')
                return redirect('jobs:detail', slug=slug)

            application.save()

            if uses_pezajob:
                notify_employer_new_application(application)
            elif uses_external:
                notify_employer_application_by_email(application, company)

            messages.success(request, 'Your application was submitted successfully. Good luck!')
            return redirect('jobs:detail', slug=slug)

        messages.error(request, 'Please correct the errors in your application form.')
        return redirect('jobs:detail', slug=slug)

    # Guest application - only reachable when the employer accepts applications by email.
    form = GuestJobApplicationForm(request.POST, request.FILES)
    if form.is_valid():
        notify_employer_guest_application(job, company, form.cleaned_data)
        messages.success(request, 'Your application was submitted successfully. Good luck!')
        return redirect('jobs:detail', slug=slug)

    messages.error(request, 'Please correct the errors in your application form.')
    return redirect('jobs:detail', slug=slug)


@login_required
@require_POST
def toggle_save_job(request, slug):
    job = get_object_or_404(Job, slug=slug)
    saved, created = SavedJob.objects.get_or_create(user=request.user, job=job)
    if not created:
        saved.delete()
        saved_state = False
        message = 'Job removed from your saved list.'
    else:
        allowed, limit_message = can_seeker_save_job(request.user)
        if not allowed:
            saved.delete()
            if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
                return JsonResponse({'saved': False, 'message': limit_message}, status=403)
            messages.error(request, limit_message)
            next_url = request.POST.get('next') or job.get_absolute_url()
            return redirect(next_url)
        saved_state = True
        message = 'Job saved to your list.'

    if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
        return JsonResponse({'saved': saved_state, 'message': message})

    messages.success(request, message)
    next_url = request.POST.get('next') or job.get_absolute_url()
    return redirect(next_url)
