import csv

from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.db.models import Avg, Count, Max, Prefetch, Q
from django.http import HttpResponse
from django.shortcuts import get_object_or_404, redirect, render
from django.utils.http import url_has_allowed_host_and_scheme
from django.views.decorators.http import require_http_methods, require_POST

from accounts.decorators import company_required
from companies.models import Company
from home.pagination import paginate
from jobs.models import Job, JobApplication
from jobs.notifications import notify_seeker_interview_scheduled
from messaging.models import Conversation
from messaging.services import get_application_messaging_context

from .forms import ApplicationNoteForm, ApplicationStatusForm, BulkStatusForm, InterviewForm
from .models import ApplicationEvent, ApplicationNote, Interview
from .pipeline_utils import PIPELINE_ORDER, build_job_boards, pipeline_stats
from .services import bulk_change_status, change_application_status, explain_match_score, log_event


def _employer_applications(user):
    return JobApplication.objects.filter(job__posted_by=user)


def _get_application(user, pk):
    return get_object_or_404(
        JobApplication.objects.select_related(
            'job', 'applicant', 'applicant__seeker_profile',
        ),
        pk=pk,
        job__posted_by=user,
    )


def _filter_applications(queryset, request):
    status = request.GET.get('status', '')
    job_slug = request.GET.get('job', '')
    q = request.GET.get('q', '').strip()

    if status in dict(JobApplication.Status.choices):
        queryset = queryset.filter(status=status)
    if job_slug:
        queryset = queryset.filter(job__slug=job_slug)
    if q:
        queryset = queryset.filter(
            Q(applicant__first_name__icontains=q)
            | Q(applicant__last_name__icontains=q)
            | Q(applicant__email__icontains=q)
            | Q(job__title__icontains=q)
            | Q(applicant__seeker_profile__skills__icontains=q)
            | Q(cv_extracted_skills__icontains=q)
        )
    return queryset


def _conversation_prefetch(user):
    return Prefetch(
        'conversation',
        queryset=Conversation.objects.annotate(
            unread_count=Count(
                'messages',
                filter=Q(messages__read_at__isnull=True) & ~Q(messages__sender_id=user.id),
            ),
        ),
    )


BOARDS_PER_PAGE = 9


def _job_boards(user):
    return build_job_boards(Job.objects.filter(posted_by=user))


@login_required
@company_required
def pipeline(request):
    company = Company.objects.filter(owner=request.user).first()
    base_qs = (
        _employer_applications(request.user)
        .select_related('job', 'applicant', 'applicant__seeker_profile')
        .prefetch_related(_conversation_prefetch(request.user))
        .order_by(*PIPELINE_ORDER)
    )
    applications = _filter_applications(base_qs, request)

    status_counts, total_count, status_tabs = pipeline_stats(
        _employer_applications(request.user)
    )

    page_obj, query_string, page_range = paginate(request, applications, per_page=20)

    employer_jobs = Job.objects.filter(posted_by=request.user).order_by('-created_at')

    job_boards = _job_boards(request.user)
    current_job = request.GET.get('job', '')
    selected_board = next((b for b in job_boards if b.slug == current_job), None)
    show_board = not (current_job or request.GET.get('status') or request.GET.get('q', '').strip()
                      or request.GET.get('view') == 'all')
    board_page = board_qs = board_range = None
    if show_board:
        board_page, board_qs, board_range = paginate(request, job_boards, per_page=BOARDS_PER_PAGE)

    return render(request, 'ats/pipeline.html', {
        'job_boards': job_boards,
        'selected_board': selected_board,
        'show_board': show_board,
        'board_page': board_page,
        'board_qs': board_qs,
        'board_range': board_range,
        'company': company,
        'applications': page_obj,
        'page_obj': page_obj,
        'query_string': query_string,
        'page_range': page_range,
        'status_choices': JobApplication.Status.choices,
        'current_status': request.GET.get('status', ''),
        'current_job': request.GET.get('job', ''),
        'search_query': request.GET.get('q', ''),
        'status_counts': status_counts,
        'status_tabs': status_tabs,
        'total_count': total_count,
        'employer_jobs': employer_jobs,
        'bulk_form': BulkStatusForm(),
    })


@login_required
@company_required
def application_detail(request, pk):
    application = _get_application(request.user, pk)
    company = Company.objects.filter(owner=request.user).first()

    status_form = ApplicationStatusForm(instance=application)
    note_form = ApplicationNoteForm()
    interview_form = InterviewForm()

    notes = application.notes.select_related('author').all()
    events = application.events.select_related('actor').all()
    interviews = application.interviews.all()

    profile = getattr(application.applicant, 'seeker_profile', None)
    chat = get_application_messaging_context(request.user, application)
    match_breakdown = explain_match_score(application)

    return render(request, 'ats/application_detail.html', {
        'company': company,
        'application': application,
        'profile': profile,
        'chat': chat,
        'match_breakdown': match_breakdown,
        'status_form': status_form,
        'note_form': note_form,
        'interview_form': interview_form,
        'notes': notes,
        'events': events,
        'interviews': interviews,
        'status_choices': JobApplication.Status.choices,
    })


@login_required
@company_required
@require_POST
def update_status(request, pk):
    application = _get_application(request.user, pk)
    # Read the current values first: validating a ModelForm overwrites them on the instance.
    old_status = application.status
    old_rating = application.rating
    form = ApplicationStatusForm(request.POST, instance=application)
    if form.is_valid():
        new_status = form.cleaned_data['status']
        new_rating = form.cleaned_data.get('rating')

        if new_status != old_status:
            change_application_status(application, new_status, request.user, old_status=old_status)
        if new_rating != old_rating:
            JobApplication.objects.filter(pk=application.pk).update(rating=new_rating)
            if new_rating:
                log_event(
                    application,
                    request.user,
                    ApplicationEvent.EventType.RATING_SET,
                    new_value=str(new_rating),
                    message=f'Rated {new_rating}/5',
                )

        messages.success(request, f'Application updated.')
    else:
        messages.error(request, 'Could not update application.')
    return redirect('ats:detail', pk=pk)


@login_required
@company_required
@require_POST
def quick_status(request, pk):
    application = _get_application(request.user, pk)
    new_status = request.POST.get('status')
    if new_status in dict(JobApplication.Status.choices):
        change_application_status(application, new_status, request.user)
        messages.success(request, f'Moved to {application.get_status_display()}.')
    else:
        messages.error(request, 'Invalid status.')
    next_url = request.POST.get('next') or request.META.get('HTTP_REFERER')
    if next_url and url_has_allowed_host_and_scheme(next_url, allowed_hosts={request.get_host()}, require_https=request.is_secure()):
        return redirect(next_url)
    return redirect('ats:detail', pk=pk)


@login_required
@company_required
@require_POST
def add_note(request, pk):
    application = _get_application(request.user, pk)
    form = ApplicationNoteForm(request.POST)
    if form.is_valid():
        note = form.save(commit=False)
        note.application = application
        note.author = request.user
        note.save()
        log_event(
            application,
            request.user,
            ApplicationEvent.EventType.NOTE_ADDED,
            message=note.body[:120],
        )
        messages.success(request, 'Note added.')
    else:
        messages.error(request, 'Note could not be saved.')
    return redirect('ats:detail', pk=pk)


@login_required
@company_required
@require_POST
def schedule_interview(request, pk):
    application = _get_application(request.user, pk)
    form = InterviewForm(request.POST)
    if form.is_valid():
        interview = form.save(commit=False)
        interview.application = application
        interview.created_by = request.user
        interview.save()

        if application.status != JobApplication.Status.INTERVIEW:
            change_application_status(application, JobApplication.Status.INTERVIEW, request.user)

        log_event(
            application,
            request.user,
            ApplicationEvent.EventType.INTERVIEW_SCHEDULED,
            message=f'Interview on {interview.scheduled_at:%d %b %Y at %H:%M}',
        )
        notify_seeker_interview_scheduled(interview)
        messages.success(request, 'Interview scheduled.')
    else:
        messages.error(request, 'Could not schedule interview. Check the date and fields.')
    return redirect('ats:detail', pk=pk)


@login_required
@company_required
@require_POST
def bulk_update(request):
    ids_raw = request.POST.get('application_ids', '')
    new_status = request.POST.get('status')
    if not ids_raw or new_status not in dict(JobApplication.Status.choices):
        messages.error(request, 'Select applications and a valid status.')
        return redirect('ats:pipeline')

    ids = [int(x) for x in ids_raw.split(',') if x.isdigit()]
    count = bulk_change_status(ids, new_status, request.user, request.user)
    messages.success(request, f'Updated {count} application(s).')
    return redirect('ats:pipeline')


@login_required
@company_required
def export_csv(request):
    applications = _filter_applications(
        _employer_applications(request.user)
        .select_related('job', 'applicant', 'applicant__seeker_profile'),
        request,
    )

    response = HttpResponse(content_type='text/csv')
    response['Content-Disposition'] = 'attachment; filename="pezajob-applications.csv"'

    writer = csv.writer(response)

    def safe(value):
        value = '' if value is None else str(value)
        return "'" + value if value[:1] in ('=', '+', '-', '@') else value

    writer.writerow([
        'ID', 'Candidate', 'Email', 'Job', 'Status', 'Match %', 'Rating',
        'Applied', 'LinkedIn', 'Location', 'Profile Skills', 'CV Skills',
    ])
    for app in applications:
        profile = getattr(app.applicant, 'seeker_profile', None)
        writer.writerow([safe(v) for v in [
            app.pk,
            app.applicant.get_full_name() or app.applicant.email,
            app.applicant.email,
            app.job.title,
            app.get_status_display(),
            app.match_score,
            app.rating or '',
            app.created_at.strftime('%Y-%m-%d'),
            app.linkedin_url,
            profile.location if profile else '',
            '; '.join(profile.skill_list) if profile else '',
            '; '.join(app.cv_skill_list),
        ]])
    return response
