"""CV library indexing, search, and bulk download."""
import io
import os
import re
import zipfile
from pathlib import Path

from django.core.files.base import ContentFile
from django.core.files.storage import default_storage
from django.db.models import Q
from django.utils import timezone
from django.utils.text import slugify

from .models import SeekerCategory, SeekerCVRecord, SeekerProfile

SAFE_FILENAME = re.compile(r'[^A-Za-z0-9._\- ]+')


def resolve_seeker_category(profile):
    from jobs.category_data import resolve_category_slug
    from jobs.models import JobCategory

    if getattr(profile, 'job_category_id', None):
        job_cat = profile.job_category
        category, _created = SeekerCategory.objects.get_or_create(
            slug=job_cat.slug,
            defaults={
                'name': job_cat.name,
                'icon': job_cat.icon or 'fa-briefcase',
                'sort_order': job_cat.sort_order,
                'is_active': job_cat.is_active,
            },
        )
        return category

    slug = resolve_category_slug(profile.category_slug or profile.category)
    job_cat = JobCategory.objects.filter(slug=slug, is_active=True).first()
    if job_cat:
        category, _created = SeekerCategory.objects.get_or_create(
            slug=job_cat.slug,
            defaults={
                'name': job_cat.name,
                'icon': job_cat.icon or 'fa-briefcase',
                'sort_order': job_cat.sort_order,
                'is_active': True,
            },
        )
        return category

    name = (profile.category or slug.replace('-', ' ').title()).strip() or 'Other / General'
    category, _created = SeekerCategory.objects.get_or_create(
        slug=slug,
        defaults={'name': name, 'icon': 'fa-briefcase'},
    )
    if category.name != name and slug != 'other-general':
        category.name = name
        category.save(update_fields=['name'])
    return category


def _seeker_email(profile):
    if profile.user_id and profile.user.email:
        return profile.user.email.strip()
    return ''


def _file_size(file_field):
    try:
        return file_field.size or 0
    except (OSError, ValueError):
        return 0


def _safe_zip_name(record):
    base = SAFE_FILENAME.sub('_', record.original_filename or f'cv_{record.pk}')
    prefix = SAFE_FILENAME.sub('_', record.full_name.replace(' ', '_'))
    category = record.category_slug or 'general'
    return f'{category}/{prefix}_{record.pk}_{base}'


def archive_previous_cv(profile, previous_file):
    """Copy the replaced CV file into the library as a non-current record."""
    if not previous_file or not previous_file.name:
        return None

    try:
        previous_file.open('rb')
        content = previous_file.read()
    except OSError:
        return None
    finally:
        try:
            previous_file.close()
        except OSError:
            pass

    if not content:
        return None

    timestamp = timezone.now().strftime('%Y%m%d%H%M%S')
    basename = os.path.basename(previous_file.name)
    archive_path = f'seekers/cv/archive/{profile.pk}/{timestamp}_{basename}'
    saved_path = default_storage.save(archive_path, ContentFile(content))

    category = resolve_seeker_category(profile)
    SeekerCVRecord.objects.filter(seeker_profile=profile, is_current=True).update(is_current=False)

    return SeekerCVRecord.objects.create(
        seeker_profile=profile,
        category=category,
        category_name=category.name,
        category_slug=category.slug,
        full_name=profile.full_name,
        seeker_email=_seeker_email(profile),
        headline=profile.headline,
        skills=profile.skills,
        location=profile.location,
        cv_file=saved_path,
        original_filename=basename,
        file_size=len(content),
        is_current=False,
    )


def index_seeker_cv(profile):
    """Create or refresh the current CV library entry for a profile."""
    if not profile.cv_file or not profile.cv_file.name:
        return None

    category = resolve_seeker_category(profile)
    basename = os.path.basename(profile.cv_file.name)

    SeekerCVRecord.objects.filter(seeker_profile=profile, is_current=True).update(is_current=False)

    existing = SeekerCVRecord.objects.filter(
        seeker_profile=profile,
        cv_file=profile.cv_file.name,
        is_current=False,
    ).order_by('-indexed_at').first()
    if existing:
        existing.is_current = True
        existing.category = category
        existing.category_name = category.name
        existing.category_slug = category.slug
        existing.full_name = profile.full_name
        existing.seeker_email = _seeker_email(profile)
        existing.headline = profile.headline
        existing.skills = profile.skills
        existing.location = profile.location
        existing.original_filename = basename
        existing.file_size = _file_size(profile.cv_file)
        existing.save()
        return existing

    return SeekerCVRecord.objects.create(
        seeker_profile=profile,
        category=category,
        category_name=category.name,
        category_slug=category.slug,
        full_name=profile.full_name,
        seeker_email=_seeker_email(profile),
        headline=profile.headline,
        skills=profile.skills,
        location=profile.location,
        cv_file=profile.cv_file,
        original_filename=basename,
        file_size=_file_size(profile.cv_file),
        is_current=True,
    )


def sync_all_seeker_cvs():
    """Backfill library records for every profile with an uploaded CV."""
    indexed = 0
    profiles = SeekerProfile.objects.filter(cv_file__isnull=False).exclude(cv_file='').select_related('user')
    for profile in profiles:
        index_seeker_cv(profile)
        indexed += 1
    return indexed


def filter_cv_records(*, q='', category_slug='', current_only=True):
    qs = SeekerCVRecord.objects.select_related('seeker_profile', 'category', 'seeker_profile__user')
    if current_only:
        qs = qs.filter(is_current=True)
    if category_slug:
        qs = qs.filter(category_slug=category_slug)
    q = (q or '').strip()
    if q:
        qs = qs.filter(
            Q(full_name__icontains=q)
            | Q(seeker_email__icontains=q)
            | Q(headline__icontains=q)
            | Q(skills__icontains=q)
            | Q(location__icontains=q)
            | Q(original_filename__icontains=q)
            | Q(category_name__icontains=q)
        )
    return qs.order_by('-indexed_at')


def get_cv_library_stats(company=None):
    """Summary counts for admin/employer dashboards."""
    from django.db.models import Count

    from subscriptions.models import SubscriptionUsageEvent
    from subscriptions.usage import company_cv_downloads_this_month, month_start

    from .models import SeekerCategory, SeekerCVRecord

    category_rows = (
        SeekerCVRecord.objects.filter(is_current=True)
        .values('category_slug', 'category_name')
        .annotate(count=Count('id'))
        .order_by('-count')[:8]
    )
    stats = {
        'total_current_cvs': SeekerCVRecord.objects.filter(is_current=True).count(),
        'total_versions': SeekerCVRecord.objects.count(),
        'active_categories': SeekerCategory.objects.filter(is_active=True).count(),
        'downloads_this_month': SubscriptionUsageEvent.objects.filter(
            event_type=SubscriptionUsageEvent.EventType.CV_DOWNLOAD,
            created_at__gte=month_start(),
        ).count(),
        'top_categories': list(category_rows),
    }
    if company:
        stats['company_downloads_this_month'] = company_cv_downloads_this_month(company)
        subscription = getattr(company, 'subscription', None)
        if subscription and subscription.is_valid:
            stats['download_limit'] = subscription.effective_benefits.cv_downloads_per_month
        else:
            stats['download_limit'] = 0
    return stats


def records_for_download(record_ids, *, limit=None, queryset=None):
    qs = queryset or SeekerCVRecord.objects.all()
    if record_ids:
        qs = qs.filter(pk__in=record_ids)
    qs = qs.filter(cv_file__gt='').order_by('-indexed_at')
    if limit not in (None, '', 'all'):
        try:
            limit = int(limit)
        except (TypeError, ValueError):
            limit = None
        if limit and limit > 0:
            qs = qs[:limit]
    return list(qs)


def build_cv_zip(records):
    """Build a zip archive from CV records. Skips missing files."""
    buffer = io.BytesIO()
    added = 0
    with zipfile.ZipFile(buffer, 'w', zipfile.ZIP_DEFLATED) as archive:
        used_names = set()
        for record in records:
            path = record.cv_file.name if record.cv_file else ''
            if not path or not default_storage.exists(path):
                continue
            arcname = _safe_zip_name(record)
            if arcname in used_names:
                stem, suffix = os.path.splitext(arcname)
                arcname = f'{stem}_{added}{suffix}'
            used_names.add(arcname)
            with default_storage.open(path, 'rb') as handle:
                archive.writestr(arcname, handle.read())
            added += 1

    if added == 0:
        return None, 0

    buffer.seek(0)
    return buffer, added


def zip_download_response(records, zip_name='pezajob_cvs.zip'):
    payload, count = build_cv_zip(records)
    if not payload:
        return None, 0

    from django.http import HttpResponse

    response = HttpResponse(payload.getvalue(), content_type='application/zip')
    response['Content-Disposition'] = f'attachment; filename="{Path(zip_name).name}"'
    return response, count
