"""ATS business logic: match scoring, status changes, audit trail."""
from django.utils import timezone

from jobs.models import JobApplication
from jobs.notifications import notify_seeker_application_status_change

from .cv_parser import extract_text_from_cv, extract_skills_from_text
from .matching_engine import score_candidate
from .models import ApplicationEvent

PROFILE_TEXT_FIELDS = (
    'skills', 'bio', 'education', 'experience_summary', 'qualification', 'headline',
)


def _job_text(job):
    return ' '.join([
        job.requirements or '',
        job.description or '',
        job.responsibilities or '',
        job.nice_to_have or '',
    ]).lower()


def _job_requirements(job):
    items = []
    for field in ('requirements', 'nice_to_have', 'responsibilities'):
        value = getattr(job, field, '') or ''
        items.extend(line.strip() for line in value.splitlines() if line.strip())
    return items


def _cv_file_for_application(application):
    if application.cv_file:
        return application.cv_file
    profile = getattr(application.applicant, 'seeker_profile', None)
    if profile and profile.cv_file:
        return profile.cv_file
    return None


def _profile_item_text(profile):
    """Text from the structured professional-profile sections (roles, certifications, products...)."""
    lines = []
    for e in profile.experiences.all():
        lines.append(' '.join(x for x in (e.title, e.company, e.industry, e.responsibilities, e.achievements) if x))
    for e in profile.education_items.all():
        lines.append(' '.join(x for x in (e.qualification, e.field_of_study, e.institution) if x))
    for c in profile.certifications.all():
        lines.append(' '.join(x for x in (c.name, c.issuer) if x))
    for p in profile.projects.all():
        lines.append(' '.join(x for x in (p.name, p.role, p.description) if x))
    if profile.product_list:
        lines.append(' '.join(profile.product_list))
    if profile.current_job_title:
        lines.append(profile.current_job_title)
    return [line for line in lines if line.strip()]


def _collect_candidate_text(application):
    """Gather text from CV files, cover letter, and profile fields."""
    sections = []
    profile = getattr(application.applicant, 'seeker_profile', None)

    cv_file = _cv_file_for_application(application)
    if cv_file:
        cv_text = extract_text_from_cv(cv_file)
        if cv_text.strip():
            sections.append(cv_text)

    if application.cover_letter.strip():
        sections.append(application.cover_letter.strip())

    if profile:
        for field in PROFILE_TEXT_FIELDS:
            value = (getattr(profile, field, '') or '').strip()
            if value:
                sections.append(value)
        sections.extend(_profile_item_text(profile))

    return '\n\n'.join(sections)


def _candidate_skills(application):
    profile = getattr(application.applicant, 'seeker_profile', None)
    skills = []
    if profile:
        skills.extend(profile.skill_list)
    skills.extend(application.cv_skill_list)

    seen = set()
    merged = []
    for skill in skills:
        key = skill.lower()
        if key not in seen:
            seen.add(key)
            merged.append(skill)
    return merged


def parse_application_content(application):
    """Extract text and skills from all candidate sources (CV, profile, cover letter)."""
    profile = getattr(application.applicant, 'seeker_profile', None)
    profile_skills = profile.skill_list if profile else []

    combined_text = _collect_candidate_text(application)
    skills = extract_skills_from_text(
        combined_text,
        job=application.job,
        profile_skills=profile_skills,
    )

    parsed_text = combined_text[:50000]
    extracted_skills = '\n'.join(skills)

    JobApplication.objects.filter(pk=application.pk).update(
        cv_parsed_text=parsed_text,
        cv_extracted_skills=extracted_skills,
        cv_parsed_at=timezone.now(),
    )
    application.cv_parsed_text = parsed_text
    application.cv_extracted_skills = extracted_skills
    application.cv_parsed_at = timezone.now()
    return {'text': parsed_text, 'skills': skills}


def compute_match_score(application):
    return explain_match_score(application)['score']


def explain_match_score(application):
    """Return match score plus a human-readable breakdown for ATS views."""
    profile = getattr(application.applicant, 'seeker_profile', None)
    candidate_text = application.cv_parsed_text or _collect_candidate_text(application)
    result = score_candidate(
        application.job,
        candidate_text,
        profile_skills=_candidate_skills(application),
        headline=getattr(profile, 'headline', '') if profile else '',
        profile_years=getattr(profile, 'years_experience', 0) if profile else 0,
        profile_qualification=getattr(profile, 'qualification', '') if profile else '',
    )
    cv_file = _cv_file_for_application(application)
    result['cv_unreadable'] = False
    result['cv_issue'] = ''
    if cv_file:
        try:
            exists = cv_file.storage.exists(cv_file.name)
        except Exception:
            exists = False
        if not exists:
            result['cv_unreadable'] = True
            result['cv_issue'] = 'The uploaded CV file is missing from storage, so it could not be read.'
        elif not extract_text_from_cv(cv_file).strip():
            result['cv_unreadable'] = True
            result['cv_issue'] = ('The uploaded CV has no readable text. It is a protected file or an image too unclear '
                                  'to read, even with text recognition.')
        if result['cv_unreadable']:
            result['summary'] += (' ' + result['cv_issue'] + ' Only the profile and cover letter were scored; '
                                  'please review the CV manually.')
    return result


def process_application_scoring(application, parse_content=True):
    """Parse all candidate content and refresh the match score."""
    if parse_content:
        parse_application_content(application)

    score = compute_match_score(application)
    if application.match_score != score:
        JobApplication.objects.filter(pk=application.pk).update(match_score=score)
        application.match_score = score
    return score


def reprocess_applications_for_user(user):
    """Re-parse and re-score every application for a seeker."""
    updated = 0
    applications = JobApplication.objects.filter(applicant=user).select_related(
        'job', 'applicant', 'applicant__seeker_profile',
    )
    for application in applications:
        process_application_scoring(application, parse_content=True)
        updated += 1
    return updated


def log_event(application, actor, event_type, old_value='', new_value='', message=''):
    return ApplicationEvent.objects.create(
        application=application,
        actor=actor,
        event_type=event_type,
        old_value=old_value,
        new_value=new_value,
        message=message,
    )


def change_application_status(application, new_status, actor, notify=True, old_status=None):
    if old_status is None:
        old_status = application.status
    if old_status == new_status:
        return application

    application.status = new_status
    application.save(update_fields=['status', 'updated_at'])

    from jobs.position_utils import sync_job_positions_on_status_change

    sync_job_positions_on_status_change(application, old_status, new_status)
    application.job.refresh_from_db(fields=['positions_filled', 'positions_total'])

    log_event(
        application,
        actor,
        ApplicationEvent.EventType.STATUS_CHANGED,
        old_value=old_status,
        new_value=new_status,
        message=(
            f'Status changed from {JobApplication.Status(old_status).label} '
            f'to {application.get_status_display()}'
        ),
    )

    if notify:
        notify_seeker_application_status_change(application)

    return application


def bulk_change_status(application_ids, new_status, actor, employer):
    applications = JobApplication.objects.filter(
        pk__in=application_ids,
        job__posted_by=employer,
    )
    updated = 0
    for application in applications:
        if application.status != new_status:
            change_application_status(application, new_status, actor)
            updated += 1
    return updated
