"""
Forms for the accounting module.
"""

from django import forms
from django.core.exceptions import ValidationError
from django.forms import BaseInlineFormSet, inlineformset_factory
from django.utils import timezone
from django.db import models
from decimal import Decimal
from datetime import date

from .models import Account, JournalEntry, JournalEntryLine, FiscalPeriod


CSS = 'mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-primary focus:ring-primary sm:text-sm'


class AccountForm(forms.ModelForm):
    """Form for creating and editing Chart of Accounts accounts."""
    
    class Meta:
        model = Account
        fields = [
            'code', 'name', 'account_type', 'subtype', 
            'description', 'parent_account', 'is_active'
        ]
        widgets = {
            'code': forms.TextInput(attrs={
                'class': CSS,
                'placeholder': 'e.g., 202001',
                'maxlength': '20',
            }),
            'name': forms.TextInput(attrs={
                'class': CSS,
                'placeholder': 'Enter account name',
            }),
            'account_type': forms.Select(attrs={'class': CSS}),
            'subtype': forms.Select(attrs={
                'class': CSS,
                'id': 'id_subtype',
            }),
            'description': forms.Textarea(attrs={
                'class': CSS,
                'rows': 3,
                'placeholder': 'Enter account description',
            }),
            'parent_account': forms.Select(attrs={
                'class': CSS,
            }),
            'is_active': forms.CheckboxInput(attrs={
                'class': 'rounded border-gray-300 text-primary focus:ring-primary',
            }),
        }
    
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        
        # Set parent_account queryset to only show active accounts
        self.fields['parent_account'].queryset = Account.objects.filter(
            is_active=True
        ).order_by('code')
        self.fields['parent_account'].empty_label = '-- No Parent (Top Level) --'
        
        # Make subtype optional
        self.fields['subtype'].required = False
        
        # Set initial value for is_active
        if not self.instance.pk:
            self.initial['is_active'] = True
        
        # Exclude current account from parent_account choices to prevent circular reference
        if self.instance.pk:
            self.fields['parent_account'].queryset = self.fields['parent_account'].queryset.exclude(
                pk=self.instance.pk
            )
            
            # Also exclude all children of this account to prevent circular reference
            children_ids = self._get_all_children_ids(self.instance)
            if children_ids:
                self.fields['parent_account'].queryset = self.fields['parent_account'].queryset.exclude(
                    pk__in=children_ids
                )
    
    def _get_all_children_ids(self, account):
        """Recursively get all children IDs of an account."""
        children_ids = []
        direct_children = Account.objects.filter(parent_account=account)
        
        for child in direct_children:
            children_ids.append(child.pk)
            children_ids.extend(self._get_all_children_ids(child))
        
        return children_ids
    
    def clean_code(self):
        """Validate that account code is unique."""
        code = self.cleaned_data.get('code')
        
        if not code:
            raise ValidationError('Account code is required.')
        
        # Check for uniqueness
        qs = Account.objects.filter(code=code)
        
        # Exclude current instance when editing
        if self.instance.pk:
            qs = qs.exclude(pk=self.instance.pk)
        
        if qs.exists():
            raise ValidationError(f'Account code "{code}" is already in use. Please choose a unique code.')
        
        return code
    
    def clean_parent_account(self):
        """Validate parent_account relationship."""
        parent_account = self.cleaned_data.get('parent_account')
        
        # If no parent, that's fine (top-level account)
        if not parent_account:
            return parent_account
        
        # Prevent self-referencing
        if self.instance.pk and parent_account.pk == self.instance.pk:
            raise ValidationError('An account cannot be its own parent.')
        
        # Check that parent account type matches
        account_type = self.cleaned_data.get('account_type')
        if account_type and parent_account.account_type != account_type:
            raise ValidationError(
                f'Parent account must be of the same type. '
                f'Parent is "{parent_account.get_account_type_display()}" '
                f'but this account is "{dict(Account.ACCOUNT_TYPES).get(account_type)}".'
            )
        
        # Check for circular reference
        if self.instance.pk:
            current = parent_account
            while current:
                if current.pk == self.instance.pk:
                    raise ValidationError(
                        'Invalid parent account: This would create a circular reference.'
                    )
                current = current.parent_account
        
        return parent_account
    
    def clean(self):
        """Additional form-level validation."""
        cleaned_data = super().clean()
        account_type = cleaned_data.get('account_type')
        subtype = cleaned_data.get('subtype')
        
        # Validate subtype is appropriate for account type
        if subtype:
            valid_subtypes = []
            
            if account_type == 'asset':
                valid_subtypes = [choice[0] for choice in Account.ASSET_SUBTYPES]
            elif account_type == 'liability':
                valid_subtypes = [choice[0] for choice in Account.LIABILITY_SUBTYPES]
            else:
                # Other account types (equity, income, expense) should not have subtypes
                self.add_error('subtype', 
                    f'{account_type.title()} accounts should not have a subtype. Please leave this field blank.'
                )
                return cleaned_data
            
            if valid_subtypes:
                # Only validate if we have valid subtypes for this account type
                if subtype not in valid_subtypes:
                    self.add_error('subtype', 
                        f'Invalid subtype for {account_type} account. '
                        f'Please select a valid subtype or leave blank.'
                    )
        
        return cleaned_data


class AccountFilterForm(forms.Form):
    """Form for filtering accounts in the list view."""
    
    search = forms.CharField(
        required=False,
        widget=forms.TextInput(attrs={
            'class': CSS,
            'placeholder': 'Search by code, name, or description...',
        }),
    )
    
    account_type = forms.ChoiceField(
        required=False,
        choices=[('', 'All Types')] + Account.ACCOUNT_TYPES,
        widget=forms.Select(attrs={'class': CSS}),
    )
    
    status = forms.ChoiceField(
        required=False,
        choices=[
            ('', 'All Statuses'),
            ('active', 'Active'),
            ('inactive', 'Inactive'),
        ],
        widget=forms.Select(attrs={'class': CSS}),
    )
    
    parent_only = forms.BooleanField(
        required=False,
        widget=forms.CheckboxInput(attrs={
            'class': 'rounded border-gray-300 text-primary focus:ring-primary',
        }),
        label='Show only parent accounts'
    )


# Journal Entry Forms

class JournalEntryForm(forms.ModelForm):
    """Form for creating and editing journal entries."""
    
    class Meta:
        model = JournalEntry
        fields = ['transaction_date', 'description', 'branch']
        widgets = {
            'transaction_date': forms.DateInput(attrs={
                'class': CSS,
                'type': 'date',
            }),
            'description': forms.Textarea(attrs={
                'class': CSS,
                'rows': 3,
                'placeholder': 'Enter transaction description...',
            }),
            'branch': forms.Select(attrs={
                'class': CSS,
            }),
        }
    
    def __init__(self, *args, **kwargs):
        self.user = kwargs.pop('user', None)
        super().__init__(*args, **kwargs)
        
        # Make branch optional for now (will be handled in view)
        self.fields['branch'].required = False
    
    def clean_transaction_date(self):
        """Validate that transaction date is not in the future and is in an open period."""
        transaction_date = self.cleaned_data.get('transaction_date')
        
        if not transaction_date:
            raise ValidationError('Transaction date is required.')
        
        # Check if date is not in the future
        if transaction_date > date.today():
            raise ValidationError('Transaction date cannot be in the future.')
        
        # Check if fiscal period is open for this date
        period = FiscalPeriod.objects.filter(
            start_date__lte=transaction_date,
            end_date__gte=transaction_date,
            status='open'
        ).first()
        
        if not period:
            raise ValidationError(
                f'The fiscal period for {transaction_date} is closed. '
                f'Cannot create journal entries for this date.'
            )
        
        return transaction_date


class JournalEntryLineFormSet(BaseInlineFormSet):
    """
    Formset for journal entry lines with validation for:
    - At least 2 line items
    - Debit and credit are mutually exclusive per line
    - Total debits equal total credits
    """
    
    def clean(self):
        """Validate the formset."""
        # Always run our custom validation, even if there are form errors
        # We'll collect our own errors
        
        # Get valid forms (non-deleted)
        valid_forms = []
        for form in self.forms:
            if form.cleaned_data and not form.cleaned_data.get('DELETE', False):
                valid_forms.append(form)
        
        # Validate at least 2 line items
        if len(valid_forms) < 2:
            raise ValidationError(
                'A journal entry must have at least 2 line items (debit and credit).'
            )
        
        # Validate each line and calculate totals
        total_debits = Decimal('0.00')
        total_credits = Decimal('0.00')
        has_mutual_exclusivity_error = False
        
        for form in valid_forms:
            data = form.cleaned_data
            debit = data.get('debit_amount', Decimal('0.00'))
            credit = data.get('credit_amount', Decimal('0.00'))
            
            # Validate debit and credit are mutually exclusive
            if debit > 0 and credit > 0:
                has_mutual_exclusivity_error = True
            
            # Validate at least one amount is provided
            if debit == 0 and credit == 0:
                raise ValidationError(
                    'Each line must have either a debit or credit amount.'
                )
            
            total_debits += debit
            total_credits += credit
        
        # Raise mutual exclusivity error after checking all forms
        if has_mutual_exclusivity_error:
            raise ValidationError(
                'Each line must have either a debit OR a credit, not both.'
            )
        
        # Validate total debits equal total credits
        if total_debits != total_credits:
            raise ValidationError(
                f'Total debits ({total_debits}) must equal total credits ({total_credits}). '
                f'Difference: {abs(total_debits - total_credits)}'
            )


class JournalEntryLineForm(forms.ModelForm):
    """Form for individual journal entry lines."""
    
    class Meta:
        model = JournalEntryLine
        fields = ['account', 'description', 'debit_amount', 'credit_amount']
        widgets = {
            'account': forms.Select(attrs={
                'class': CSS + ' account-select',
            }),
            'description': forms.TextInput(attrs={
                'class': CSS,
                'placeholder': 'Line description...',
            }),
            'debit_amount': forms.NumberInput(attrs={
                'class': CSS + ' debit-input',
                'step': '0.01',
                'min': '0',
                'placeholder': '0.00',
            }),
            'credit_amount': forms.NumberInput(attrs={
                'class': CSS + ' credit-input',
                'step': '0.01',
                'min': '0',
                'placeholder': '0.00',
            }),
        }
    
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        
        # Only show active accounts
        self.fields['account'].queryset = Account.objects.filter(
            is_active=True
        ).order_by('code')
        
        # Make amounts not required (will be validated in formset clean method)
        self.fields['debit_amount'].required = False
        self.fields['credit_amount'].required = False


# Create the inline formset
JournalEntryLineInlineFormSet = inlineformset_factory(
    JournalEntry,
    JournalEntryLine,
    form=JournalEntryLineForm,
    formset=JournalEntryLineFormSet,
    extra=4,  # Show 4 empty forms initially
    can_delete=True,
    min_num=2,  # Require at least 2 lines
    validate_min=True,
)


class JournalEntryFilterForm(forms.Form):
    """Form for filtering journal entries in the list view."""
    
    status = forms.ChoiceField(
        required=False,
        choices=[('', 'All Statuses')] + JournalEntry.STATUS_CHOICES,
        widget=forms.Select(attrs={'class': CSS}),
    )
    
    start_date = forms.DateField(
        required=False,
        widget=forms.DateInput(attrs={
            'class': CSS,
            'type': 'date',
        }),
        label='From Date'
    )
    
    end_date = forms.DateField(
        required=False,
        widget=forms.DateInput(attrs={
            'class': CSS,
            'type': 'date',
        }),
        label='To Date'
    )
    
    branch = forms.ModelChoiceField(
        required=False,
        queryset=None,  # Will be set in __init__
        widget=forms.Select(attrs={'class': CSS}),
        empty_label='All Branches',
    )
    
    def __init__(self, *args, **kwargs):
        user = kwargs.pop('user', None)
        super().__init__(*args, **kwargs)
        
        # Set branch queryset based on user permissions
        if user:
            from users.models import Branch
            # For now, show all branches - implement branch access control as needed
            self.fields['branch'].queryset = Branch.objects.all().order_by('name')


class FiscalPeriodForm(forms.ModelForm):
    """Form for creating and editing fiscal periods."""
    
    class Meta:
        model = FiscalPeriod
        fields = ['name', 'period_type', 'start_date', 'end_date']
        widgets = {
            'name': forms.TextInput(attrs={
                'class': CSS,
                'placeholder': 'e.g., January 2024, Q1 2024, FY 2024',
            }),
            'period_type': forms.Select(attrs={'class': CSS}),
            'start_date': forms.DateInput(attrs={
                'class': CSS,
                'type': 'date',
            }),
            'end_date': forms.DateInput(attrs={
                'class': CSS,
                'type': 'date',
            }),
        }
    
    def clean_end_date(self):
        """Validate that end_date is after start_date."""
        start_date = self.cleaned_data.get('start_date')
        end_date = self.cleaned_data.get('end_date')
        
        if start_date and end_date:
            if end_date <= start_date:
                raise ValidationError(
                    'End date must be after start date.'
                )
        
        return end_date
    
    def clean(self):
        """Validate that period doesn't overlap with existing periods."""
        cleaned_data = super().clean()
        start_date = cleaned_data.get('start_date')
        end_date = cleaned_data.get('end_date')
        
        if start_date and end_date:
            # Check for overlapping periods
            overlapping_periods = FiscalPeriod.objects.filter(
                # Period starts during this period
                models.Q(start_date__range=(start_date, end_date)) |
                # Period ends during this period
                models.Q(end_date__range=(start_date, end_date)) |
                # Period completely contains this period
                models.Q(start_date__lte=start_date, end_date__gte=end_date)
            )
            
            # Exclude current instance when editing
            if self.instance.pk:
                overlapping_periods = overlapping_periods.exclude(pk=self.instance.pk)
            
            if overlapping_periods.exists():
                period = overlapping_periods.first()
                raise ValidationError(
                    f'This period overlaps with existing period: '
                    f'"{period.name}" ({period.start_date} to {period.end_date}). '
                    f'Periods cannot overlap.'
                )
        
        return cleaned_data
