from django import forms
from django.utils import timezone

from taxonomy.helpers import active_tree, industry_choices
from companies.models import Company
from home.geo.form_mixins import JobLocationFormMixin, normalize_location_post_data
from jobs.attachments import validate_attachment_file
from jobs.category_data import CATEGORY_HELP_TEXT, active_job_categories, numbered_category_label
from jobs.experience_utils import (
    EXPERIENCE_YEAR_MAX,
    experience_year_field_choices,
    format_experience_display,
    level_from_years,
    years_from_level,
)
from jobs.application_requirements import (
    CANDIDATE_FIELD_CHOICES,
    DEFAULT_CANDIDATE_FIELDS,
    DEFAULT_DOCUMENTS,
    DOCUMENT_CHOICES,
    normalize_candidate_fields,
    normalize_documents,
    parse_screening_questions,
    screening_questions_to_json,
)
from jobs.models import Job, JobApplication
from subscriptions.usage import company_has_ats_subscription


def _apply_ongoing_vacancy(cleaned_data):
    """Map the ongoing vacancy checkbox to a null deadline."""
    if cleaned_data.get('ongoing_vacancy'):
        cleaned_data['deadline'] = None
    return cleaned_data


class MultipleFileInput(forms.ClearableFileInput):
    allow_multiple_selected = True


class MultipleFileField(forms.FileField):
    def __init__(self, *args, **kwargs):
        kwargs.setdefault('widget', MultipleFileInput(attrs={
            'class': 'form-control',
            'accept': 'image/jpeg,image/png,image/gif,image/webp,image/bmp,.pdf,.doc,.docx',
            'multiple': True,
        }))
        super().__init__(*args, **kwargs)

    def clean(self, data, initial=None):
        if not data:
            return []
        if not isinstance(data, (list, tuple)):
            data = [data]
        cleaned = []
        for item in data:
            if not item:
                continue
            validate_attachment_file(item)
            cleaned.append(item)
        return cleaned


class JobPostForm(JobLocationFormMixin, forms.ModelForm):
    application_methods = forms.MultipleChoiceField(
        choices=Job.ApplicationMethod.choices,
        label='',
        required=False,
        widget=forms.CheckboxSelectMultiple(attrs={'class': 'application-method-checkbox'}),
    )
    application_candidate_fields = forms.MultipleChoiceField(
        choices=CANDIDATE_FIELD_CHOICES,
        label='Candidate information',
        required=False,
        widget=forms.CheckboxSelectMultiple(attrs={'class': 'application-req-checkbox'}),
    )
    application_documents = forms.MultipleChoiceField(
        choices=DOCUMENT_CHOICES,
        label='Documents',
        required=False,
        widget=forms.CheckboxSelectMultiple(attrs={'class': 'application-req-checkbox'}),
    )
    use_screening_questions = forms.ChoiceField(
        choices=[('no', 'No'), ('yes', 'Yes')],
        initial='no',
        required=False,
        label='Would you like to ask candidates additional questions?',
        widget=forms.RadioSelect(attrs={'class': 'screening-toggle-radio'}),
    )
    screening_questions_json = forms.CharField(
        required=False,
        label='',
        widget=forms.HiddenInput(attrs={'id': 'id_screening_questions_json'}),
    )
    experience_years_min = forms.TypedChoiceField(
        label='Minimum years',
        choices=experience_year_field_choices(),
        coerce=int,
        widget=forms.Select(attrs={'class': 'form-control', 'id': 'id_experience_years_min'}),
    )
    experience_years_max = forms.TypedChoiceField(
        label='Maximum years (optional)',
        choices=experience_year_field_choices(),
        coerce=int,
        widget=forms.Select(attrs={'class': 'form-control', 'id': 'id_experience_years_max'}),
    )
    attachment_files = MultipleFileField(
        required=False,
        label='Images & documents',
        help_text=(
            'Optional. Upload recruitment flyers (JPG, PNG, WEBP, GIF) and job description '
            'documents (PDF, DOC, DOCX). You can select multiple files. Max 10 MB each.'
        ),
    )
    clear_company_logo = forms.BooleanField(
        required=False,
        label='Remove company logo',
    )
    ongoing_vacancy = forms.BooleanField(
        required=False,
        label='Ongoing Vacancy',
    )

    def __init__(self, *args, company=None, user=None, **kwargs):
        self._company = company
        self._user = user
        if kwargs.get('data'):
            kwargs['data'] = normalize_location_post_data(kwargs['data'])
        elif args:
            args = (normalize_location_post_data(args[0]), *args[1:])
        super().__init__(*args, **kwargs)
        self.experience_year_max = EXPERIENCE_YEAR_MAX
        if self.instance.pk:
            self.fields['ongoing_vacancy'].initial = self.instance.is_ongoing
        self.fields['title'].label = 'Job Title'
        self.fields['company_industry'] = forms.ChoiceField(
            choices=industry_choices(),
            label='Industry',
            help_text='Select the industry your organisation operates in.',
            widget=forms.Select(attrs={'class': 'form-control'}),
        )
        category_field = forms.ModelChoiceField(
            queryset=active_job_categories(),
            label='Job Category',
            help_text=CATEGORY_HELP_TEXT,
            empty_label='Select a job category…',
            widget=forms.Select(attrs={'class': 'form-control'}),
        )
        category_field.label_from_instance = numbered_category_label
        self.fields['category'] = category_field
        if self.instance.pk and self.instance.category_id:
            self.fields['category'].initial = self.instance.category_id
        if self.instance.pk and self.instance.company_industry:
            self.fields['company_industry'].initial = self.instance.company_industry
        elif company and company.industry in active_tree():
            self.fields['company_industry'].initial = company.industry
        self.init_job_location_fields(company=company)
        self.fields['location'].required = False
        self.fields['job_type'].help_text = (
            'Choose Remote to work from anywhere within the location you specify below.'
        )
        self.fields['deadline'].required = False
        self.fields['deadline'].label = 'Application deadline'
        self.fields['deadline'].help_text = (
            'Optional. When set, the listing closes automatically after this date. '
            'Use the ongoing vacancy option below for roles with no fixed deadline.'
        )
        self.fields['how_to_apply'].required = False
        self.fields['how_to_apply'].label = 'Application instructions'
        self.fields['how_to_apply'].help_text = (
            'Website URL, email address, or instructions for candidates applying outside PezaJob.'
        )
        self.can_use_pezajob_ats = company_has_ats_subscription(company, user=user)
        application_methods_field = self.fields['application_methods']
        application_methods_field.label = ''
        if not self.can_use_pezajob_ats:
            application_methods_field.choices = [
                (Job.ApplicationMethod.EXTERNAL, Job.ApplicationMethod.EXTERNAL.label),
            ]
        if self.data.get('application_methods'):
            pass
        elif self.instance.pk:
            application_methods_field.initial = self.instance.get_application_methods()
        elif self.can_use_pezajob_ats:
            application_methods_field.initial = [
                Job.ApplicationMethod.PEZAJOB,
                Job.ApplicationMethod.EXTERNAL,
            ]
        application_methods_field.widget.choices = application_methods_field.choices
        candidate_fields = self.fields['application_candidate_fields']
        candidate_fields.label = ''
        document_fields = self.fields['application_documents']
        document_fields.label = ''
        if self.instance.pk and self.instance.application_candidate_fields:
            candidate_fields.initial = self.instance.application_candidate_fields
        elif not self.data.get('application_candidate_fields'):
            candidate_fields.initial = list(DEFAULT_CANDIDATE_FIELDS)
        if self.instance.pk and self.instance.application_documents:
            document_fields.initial = self.instance.application_documents
        elif not self.data.get('application_documents'):
            document_fields.initial = list(DEFAULT_DOCUMENTS)
        if self.instance.pk:
            self.fields['use_screening_questions'].initial = (
                'yes' if self.instance.screening_questions_enabled else 'no'
            )
            self.fields['screening_questions_json'].initial = screening_questions_to_json(
                self.instance.screening_questions
            )
        min_years, max_years = years_from_level(Job.ExperienceLevel.MID)
        if self.instance.pk:
            if self.instance.experience_years_min is not None and self.instance.experience_years_max is not None:
                min_years = self.instance.experience_years_min
                max_years = self.instance.experience_years_max
            else:
                min_years, max_years = years_from_level(self.instance.experience_level)
        elif self.data.get('experience_years_min') and self.data.get('experience_years_max'):
            min_years = int(self.data['experience_years_min'])
            max_years = int(self.data['experience_years_max'])
        self.fields['experience_years_min'].initial = min_years
        self.fields['experience_years_max'].initial = max_years
        self.fields['experience_years_min'].help_text = ''
        self.fields['experience_years_max'].help_text = ''
        self.fields['positions_total'].label = 'Number of positions'
        positions_help = 'Enter the number of people you are recruiting for this role.'
        if self.instance.pk and self.instance.positions_filled:
            positions_help = (
                f'{positions_help} {self.instance.positions_filled} hired so far, '
                f'{self.instance.positions_remaining} still open.'
            )
        self.fields['positions_total'].help_text = positions_help
        self.fields['salary_min'].required = False
        self.fields['salary_max'].required = False
        self.fields['salary_min'].label = 'Minimum salary (optional)'
        self.fields['salary_max'].label = 'Maximum salary (optional)'
        salary_help = 'Enter one amount or a range. Leave both blank for Negotiable.'
        self.fields['salary_min'].help_text = salary_help
        self.fields['salary_max'].help_text = salary_help
        self.fields['company_logo'].required = False
        self.fields['company_logo'].label = 'Company logo (optional)'
        self.fields['company_logo'].help_text = (
            'Upload a logo when posting for a company. If left blank, your profile photo is used on the listing.'
        )
        if not self.instance.pk or not self.instance.company_logo:
            self.fields.pop('clear_company_logo', None)
        elif 'clear_company_logo' in self.fields:
            clear_field = self.fields.pop('clear_company_logo')
            self.fields['clear_company_logo'] = clear_field

    def _current_application_methods(self):
        if self.is_bound:
            methods = self.data.getlist('application_methods')
        else:
            methods = self.fields['application_methods'].initial or []
        return [str(value) for value in methods]

    @property
    def initial_pezajob_selected(self):
        return Job.ApplicationMethod.PEZAJOB in self._current_application_methods()

    @property
    def initial_external_selected(self):
        return Job.ApplicationMethod.EXTERNAL in self._current_application_methods()

    @property
    def initial_screening_enabled(self):
        if self.is_bound and 'use_screening_questions' in self.data:
            return self.data.get('use_screening_questions') == 'yes'
        return self.fields['use_screening_questions'].initial == 'yes'

    def clean(self):
        cleaned_data = super().clean()
        cleaned_data = self.apply_job_location_validation(cleaned_data)
        cleaned_data = _apply_ongoing_vacancy(cleaned_data)
        ongoing = cleaned_data.get('ongoing_vacancy')
        deadline = cleaned_data.get('deadline')
        if not ongoing and not deadline:
            self.add_error(
                'ongoing_vacancy',
                'Choose an application deadline or mark this as an ongoing vacancy.',
            )
        if self.data.get('ongoing_vacancy') and self.data.get('deadline'):
            self.add_error(
                'deadline',
                'Clear the deadline or uncheck ongoing vacancy - they cannot both be set.',
            )
        salary_min = cleaned_data.get('salary_min')
        salary_max = cleaned_data.get('salary_max')
        if salary_min is not None and salary_max is not None and salary_min > salary_max:
            self.add_error('salary_max', 'Maximum salary must be greater than or equal to minimum salary.')
        exp_min = cleaned_data.get('experience_years_min')
        exp_max = cleaned_data.get('experience_years_max')
        if exp_min is None:
            self.add_error('experience_years_min', 'Select the minimum years of experience.')
        if exp_max is None:
            self.add_error('experience_years_max', 'Select the maximum years of experience.')
        if exp_min is not None and exp_max is not None and exp_min > exp_max:
            self.add_error(
                'experience_years_max',
                'Maximum years must be greater than or equal to minimum years.',
            )
        positions_total = cleaned_data.get('positions_total')
        if positions_total is not None and positions_total < 1:
            self.add_error('positions_total', 'Enter at least one position.')
        if (
            positions_total is not None
            and self.instance.pk
            and positions_total < self.instance.positions_filled
        ):
            self.add_error(
                'positions_total',
                f'Cannot be less than {self.instance.positions_filled} people already hired.',
            )
        application_methods = cleaned_data.get('application_methods') or []
        how_to_apply = (cleaned_data.get('how_to_apply') or '').strip()
        if not application_methods:
            self.add_error('application_methods', 'Select at least one application method.')
        if Job.ApplicationMethod.PEZAJOB in application_methods and not getattr(
            self, 'can_use_pezajob_ats', False
        ):
            self.add_error(
                'application_methods',
                'PezaJob applications require a subscription plan with ATS access.',
            )
        if Job.ApplicationMethod.EXTERNAL in application_methods and not how_to_apply:
            self.add_error(
                'how_to_apply',
                'Enter application instructions (website URL or email address).',
            )
        if Job.ApplicationMethod.PEZAJOB in application_methods:
            documents = cleaned_data.get('application_documents') or []
            if not documents:
                self.add_error(
                    'application_documents',
                    'Select at least one document for PezaJob applications.',
                )
            use_screening = cleaned_data.get('use_screening_questions') == 'yes'
            questions = parse_screening_questions(cleaned_data.get('screening_questions_json'))
            if use_screening and not questions:
                self.add_error(
                    'use_screening_questions',
                    'Add at least one screening question or choose No.',
                )
            cleaned_data['_screening_questions'] = questions if use_screening else []
        return cleaned_data

    def clean_deadline(self):
        deadline = self.cleaned_data.get('deadline')
        if deadline and deadline < timezone.localdate():
            raise forms.ValidationError('Application deadline must be today or a future date.')
        return deadline

    def save(self, commit=True):
        job = super().save(commit=False)
        new_logo = self.cleaned_data.get('company_logo')
        if self.cleaned_data.get('clear_company_logo') and not new_logo and job.company_logo:
            job.company_logo.delete(save=False)
            job.company_logo = None
        self.persist_structured_location(job)
        exp_min = self.cleaned_data.get('experience_years_min')
        exp_max = self.cleaned_data.get('experience_years_max')
        if exp_min is not None and exp_max is not None:
            job.experience_years_min = exp_min
            job.experience_years_max = exp_max
            job.experience_level = level_from_years(exp_min, exp_max)
            job.experience_required = format_experience_display(exp_min, exp_max)
        methods = self.cleaned_data.get('application_methods') or []
        job.application_methods = list(methods)
        if Job.ApplicationMethod.PEZAJOB in methods:
            job.application_method = Job.ApplicationMethod.PEZAJOB
            job.application_candidate_fields = normalize_candidate_fields(
                self.cleaned_data.get('application_candidate_fields')
            )
            job.application_documents = normalize_documents(
                self.cleaned_data.get('application_documents')
            )
            job.screening_questions_enabled = (
                self.cleaned_data.get('use_screening_questions') == 'yes'
            )
            job.screening_questions = self.cleaned_data.get('_screening_questions') or []
        else:
            job.application_candidate_fields = []
            job.application_documents = []
            job.screening_questions_enabled = False
            job.screening_questions = []
        if methods and Job.ApplicationMethod.PEZAJOB not in methods:
            job.application_method = methods[0]
        elif not methods:
            job.application_method = Job.ApplicationMethod.EXTERNAL
        if commit:
            job.save()
        return job

    class Meta:
        model = Job
        fields = [
            'title',
            'company_industry',
            'category',
            'job_type',
            'location',
            'experience_years_min',
            'experience_years_max',
            'positions_total',
            'salary_min',
            'salary_max',
            'company_logo',
            'description',
            'responsibilities',
            'requirements',
            'benefits',
            'how_to_apply',
            'deadline',
        ]
        widgets = {
            'title': forms.TextInput(attrs={'class': 'form-control'}),
            'location': forms.HiddenInput(),
            'job_type': forms.Select(attrs={'class': 'form-control'}),
            'positions_total': forms.NumberInput(attrs={'class': 'form-control', 'min': 1, 'step': 1}),
            'salary_min': forms.NumberInput(attrs={'class': 'form-control', 'min': 0}),
            'salary_max': forms.NumberInput(attrs={'class': 'form-control', 'min': 0}),
            'company_logo': forms.FileInput(attrs={'class': 'form-control', 'accept': 'image/*'}),
            'description': forms.Textarea(attrs={'class': 'form-control', 'rows': 4}),
            'responsibilities': forms.Textarea(attrs={'class': 'form-control', 'rows': 4, 'placeholder': 'One responsibility per line'}),
            'requirements': forms.Textarea(attrs={'class': 'form-control', 'rows': 4, 'placeholder': 'One requirement per line'}),
            'benefits': forms.Textarea(attrs={'class': 'form-control', 'rows': 3, 'placeholder': 'One benefit per line'}),
            'how_to_apply': forms.Textarea(attrs={'class': 'form-control', 'rows': 3, 'placeholder': 'e.g. Send CV to careers@company.com or apply via link'}),
            'deadline': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
        }

class AdminJobPostForm(JobPostForm):
    company_name = forms.CharField(
        max_length=150,
        label='Company name',
        help_text='Employer or organization shown on the job listing.',
        widget=forms.TextInput(attrs={'class': 'form-control'}),
    )

    class Meta(JobPostForm.Meta):
        fields = ['company_name'] + JobPostForm.Meta.fields

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        if self.instance.pk:
            self.fields['company_name'].initial = self.instance.company_name


class CompanyProfileForm(forms.ModelForm):
    clear_cover_image = forms.BooleanField(
        required=False,
        label='Remove cover photo',
    )

    class Meta:
        model = Company
        fields = [
            'cover_image',
            'name',
            'industry',
            'location',
            'description',
            'website',
            'email',
            'phone',
            'linkedin_url',
            'application_delivery_method',
            'employee_count_display',
            'company_size',
            'founded_year',
            'tags',
            'is_public',
            'is_online',
        ]
        widgets = {
            'cover_image': forms.FileInput(attrs={'class': 'form-control', 'accept': 'image/*'}),
            'name': forms.TextInput(attrs={'class': 'form-control'}),
            'industry': forms.TextInput(attrs={'class': 'form-control'}),
            'location': forms.TextInput(attrs={'class': 'form-control'}),
            'description': forms.Textarea(attrs={'class': 'form-control', 'rows': 5}),
            'website': forms.URLInput(attrs={'class': 'form-control'}),
            'email': forms.EmailInput(attrs={'class': 'form-control'}),
            'phone': forms.TextInput(attrs={'class': 'form-control'}),
            'linkedin_url': forms.URLInput(attrs={'class': 'form-control'}),
            'application_delivery_method': forms.Select(attrs={'class': 'form-control'}),
            'employee_count_display': forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'e.g. 500+ employees'}),
            'company_size': forms.Select(attrs={'class': 'form-control'}),
            'founded_year': forms.NumberInput(attrs={'class': 'form-control', 'min': 1900}),
            'tags': forms.Textarea(attrs={'class': 'form-control', 'rows': 3, 'placeholder': 'One tag per line'}),
            'is_public': forms.CheckboxInput(),
            'is_online': forms.CheckboxInput(),
        }

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields['cover_image'].label = 'Cover photo'
        self.fields['cover_image'].help_text = (
            'Banner image shown on your company page and homepage carousel. '
            'Recommended size: 1200×400 px or wider.'
        )
        self.fields['is_public'].label = 'Public company page'
        self.fields['is_public'].help_text = (
            'When enabled, your company appears in the company directory and has a public page.'
        )
        self.fields['is_online'].label = 'Show as online'
        self.fields['is_online'].help_text = (
            'When disabled, your company appears offline and is hidden from the homepage carousel.'
        )
        self.fields['application_delivery_method'].label = 'Receive applications via'
        if company_has_ats_subscription(self.instance):
            self.fields['application_delivery_method'].help_text = (
                'Choose whether new applications land in your PEZAJOB pipeline or go straight to your email above.'
            )
        else:
            self.fields['application_delivery_method'].choices = [
                (Company.ApplicationDelivery.EMAIL, Company.ApplicationDelivery.EMAIL.label),
            ]
            self.fields['application_delivery_method'].disabled = True
            self.fields['application_delivery_method'].help_text = (
                'Applications are sent to your email above. Upgrade to a Professional, Recruiter Pro, '
                'or Enterprise plan to receive and manage applications through the PEZAJOB ATS instead.'
            )

    def save(self, commit=True):
        company = super().save(commit=False)
        if self.cleaned_data.get('clear_cover_image') and company.cover_image:
            company.cover_image.delete(save=False)
            company.cover_image = None
        if commit:
            company.save()
        return company


class ApplicationStatusForm(forms.ModelForm):
    class Meta:
        model = JobApplication
        fields = ['status']
        widgets = {
            'status': forms.Select(attrs={'class': 'form-control', 'style': 'min-width:160px;'}),
        }


class ReactivateJobForm(forms.ModelForm):
    ongoing_vacancy = forms.BooleanField(
        required=False,
        label='Ongoing Vacancy',
    )

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields['ongoing_vacancy'].initial = self.instance.is_ongoing
        self.fields['deadline'].required = False
        self.fields['deadline'].label = 'Application deadline'
        if self.instance.needs_deadline_to_reactivate:
            self.fields['deadline'].help_text = (
                'Required unless you reopen as an ongoing vacancy (no fixed deadline).'
            )
        else:
            self.fields['deadline'].help_text = (
                'Optional. Set a new deadline, or use ongoing vacancy to keep the listing open-ended.'
            )

    class Meta:
        model = Job
        fields = ['deadline']
        widgets = {
            'deadline': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
        }

    def clean(self):
        cleaned_data = super().clean()
        cleaned_data = _apply_ongoing_vacancy(cleaned_data)
        ongoing = cleaned_data.get('ongoing_vacancy')
        deadline = cleaned_data.get('deadline')
        if not ongoing and not deadline and self.instance.needs_deadline_to_reactivate:
            self.add_error(
                'ongoing_vacancy',
                'Choose a new application deadline or mark this as an ongoing vacancy.',
            )
        if self.data.get('ongoing_vacancy') and self.data.get('deadline'):
            self.add_error(
                'deadline',
                'Clear the deadline or uncheck ongoing vacancy - they cannot both be set.',
            )
        return cleaned_data

    def clean_deadline(self):
        deadline = self.cleaned_data.get('deadline')
        if deadline and deadline < timezone.localdate():
            raise forms.ValidationError('Application deadline must be today or a future date.')
        return deadline
