"""
Fiscal Period Management Models

Manages financial year and period operations including:
- Fiscal year definition and configuration
- Period opening and closing
- Period lock status
- Financial statement archival
- Year-end closing processes

Complies with:
- IAS 1 - Presentation of Financial Statements
- IAS 10 - Events After the Reporting Period
- SOX compliance for period locking
"""

from datetime import datetime, date
from typing import Optional, Dict, Any
import uuid
import enum
from sqlalchemy import (
    Column, String, Boolean, Text, DateTime, Date,
    ForeignKey, Numeric, Integer, CheckConstraint, Enum, JSON
)
from sqlalchemy.orm import relationship, validates

from app.models.base import BaseModel


class FiscalYearStatus(str, enum.Enum):
    """Fiscal year status"""
    DRAFT = "draft"  # Year defined but not started
    OPEN = "open"  # Currently active year
    CLOSED = "closed"  # Year-end closed
    ARCHIVED = "archived"  # Historical, read-only


class PeriodType(str, enum.Enum):
    """Period type classification"""
    MONTHLY = "monthly"
    QUARTERLY = "quarterly"
    YEARLY = "yearly"
    CUSTOM = "custom"


class PeriodStatus(str, enum.Enum):
    """Period status"""
    DRAFT = "draft"  # Period defined but not opened
    OPEN = "open"  # Transactions allowed
    SOFT_CLOSED = "soft_closed"  # Closed but can be reopened by Finance Manager
    HARD_CLOSED = "hard_closed"  # Permanently closed, requires CFO to reopen
    LOCKED = "locked"  # Locked for audit, no changes allowed


class FiscalYear(BaseModel):
    """
    Fiscal year definition

    Represents a complete financial year with configurable start/end dates.
    Supports calendar year, April-March, or any custom fiscal year pattern.
    """
    __tablename__ = "fiscal_years"

    __table_args__ = (
        CheckConstraint(
            "start_date < end_date",
            name='valid_fiscal_year_dates'
        ),
        {
            'comment': 'Financial year definitions and configurations'
        }
    )

    id = Column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))

    # Year identification
    year_name = Column(String(100), nullable=False, unique=True,
                      comment='Display name (e.g., "FY 2025-2026", "Year 2025")')

    year_code = Column(String(20), nullable=False, unique=True, index=True,
                      comment='Short code (e.g., "FY2025", "2025")')

    # Date range
    start_date = Column(Date, nullable=False, index=True,
                       comment='First day of fiscal year')

    end_date = Column(Date, nullable=False, index=True,
                     comment='Last day of fiscal year')

    # Status and control
    status = Column(Enum(FiscalYearStatus),
                   default=FiscalYearStatus.DRAFT,
                   nullable=False,
                   index=True,
                   comment='Current status of the fiscal year')

    is_active = Column(Boolean, default=False, nullable=False,
                      comment='Whether this is the currently active fiscal year')

    # Closure tracking
    closed_date = Column(DateTime, nullable=True,
                        comment='When the year was closed')

    closed_by_user_id = Column(String(36), ForeignKey("users.id"), nullable=True,
                              comment='User who closed the year')

    # Archival
    archived_date = Column(DateTime, nullable=True,
                          comment='When the year was archived')

    archived_by_user_id = Column(String(36), ForeignKey("users.id"), nullable=True,
                                comment='User who archived the year')

    # Configuration
    allow_backdated_transactions = Column(Boolean, default=False,
                                         comment='Allow posting to previous periods')

    auto_create_periods = Column(Boolean, default=True,
                                comment='Automatically create monthly periods')

    period_type_default = Column(Enum(PeriodType),
                                default=PeriodType.MONTHLY,
                                comment='Default period breakdown type')

    # Metadata
    notes = Column(Text, nullable=True,
                  comment='Additional notes or configuration')

    settings = Column(JSON, nullable=True,
                     comment='Additional settings as JSON')

    # Branch support (if multi-entity)
    branch_id = Column(String(36), ForeignKey("branches.id"), nullable=True,
                      comment='Branch if using separate fiscal years per branch')

    # Relationships
    periods = relationship("FiscalPeriod",
                          back_populates="fiscal_year",
                          cascade="all, delete-orphan",
                          order_by="FiscalPeriod.period_number")

    closed_by = relationship("User", foreign_keys=[closed_by_user_id])
    archived_by = relationship("User", foreign_keys=[archived_by_user_id])
    branch = relationship("Branch")

    @validates('start_date', 'end_date')
    def validate_dates(self, key, value):
        """Ensure dates are valid"""
        if key == 'end_date' and hasattr(self, 'start_date') and self.start_date:
            if value <= self.start_date:
                raise ValueError("End date must be after start date")
        return value

    def is_date_in_year(self, check_date: date) -> bool:
        """Check if a date falls within this fiscal year"""
        return self.start_date <= check_date <= self.end_date

    def can_post_transactions(self) -> bool:
        """Check if transactions can be posted to this year"""
        return self.status in [FiscalYearStatus.OPEN, FiscalYearStatus.DRAFT]

    def to_dict(self) -> Dict[str, Any]:
        """Convert to dictionary"""
        return {
            'id': self.id,
            'year_name': self.year_name,
            'year_code': self.year_code,
            'start_date': self.start_date.isoformat() if self.start_date else None,
            'end_date': self.end_date.isoformat() if self.end_date else None,
            'status': self.status.value if self.status else None,
            'is_active': self.is_active,
            'closed_date': self.closed_date.isoformat() if self.closed_date else None,
            'archived_date': self.archived_date.isoformat() if self.archived_date else None,
            'allow_backdated_transactions': self.allow_backdated_transactions,
            'period_type_default': self.period_type_default.value if self.period_type_default else None,
            'notes': self.notes,
            'created_at': self.created_at.isoformat() if self.created_at else None,
            'updated_at': self.updated_at.isoformat() if self.updated_at else None,
        }


class FiscalPeriod(BaseModel):
    """
    Fiscal period (month, quarter, etc.)

    Represents a sub-period within a fiscal year. Periods can be opened,
    closed, and locked independently for granular control.
    """
    __tablename__ = "fiscal_periods"

    __table_args__ = (
        CheckConstraint(
            "start_date < end_date",
            name='valid_period_dates'
        ),
        {
            'comment': 'Financial periods within fiscal years'
        }
    )

    id = Column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))

    # Parent fiscal year
    fiscal_year_id = Column(String(36),
                           ForeignKey("fiscal_years.id", ondelete='CASCADE'),
                           nullable=False,
                           index=True)

    # Period identification
    period_name = Column(String(100), nullable=False,
                        comment='Display name (e.g., "January 2025", "Q1 2025")')

    period_code = Column(String(20), nullable=False, index=True,
                        comment='Short code (e.g., "2025-01", "2025-Q1")')

    period_number = Column(Integer, nullable=False,
                          comment='Sequential number within fiscal year (1, 2, 3...)')

    period_type = Column(Enum(PeriodType),
                        default=PeriodType.MONTHLY,
                        nullable=False,
                        comment='Type of period')

    # Date range
    start_date = Column(Date, nullable=False, index=True,
                       comment='First day of period')

    end_date = Column(Date, nullable=False, index=True,
                     comment='Last day of period')

    # Status and control
    status = Column(Enum(PeriodStatus),
                   default=PeriodStatus.DRAFT,
                   nullable=False,
                   index=True,
                   comment='Current status of the period')

    is_locked = Column(Boolean, default=False, nullable=False,
                      comment='Whether period is locked (no changes allowed)')

    # Opening tracking
    opened_date = Column(DateTime, nullable=True,
                        comment='When the period was opened')

    opened_by_user_id = Column(String(36), ForeignKey("users.id"), nullable=True,
                              comment='User who opened the period')

    # Closure tracking
    soft_closed_date = Column(DateTime, nullable=True,
                             comment='When soft close was performed')

    soft_closed_by_user_id = Column(String(36), ForeignKey("users.id"), nullable=True,
                                   comment='User who performed soft close')

    hard_closed_date = Column(DateTime, nullable=True,
                             comment='When hard close was performed')

    hard_closed_by_user_id = Column(String(36), ForeignKey("users.id"), nullable=True,
                                   comment='User who performed hard close')

    # Lock tracking
    locked_date = Column(DateTime, nullable=True,
                        comment='When the period was locked')

    locked_by_user_id = Column(String(36), ForeignKey("users.id"), nullable=True,
                              comment='User who locked the period')

    locked_reason = Column(Text, nullable=True,
                          comment='Reason for locking (e.g., audit, compliance)')

    # Reopening tracking (audit trail)
    last_reopened_date = Column(DateTime, nullable=True,
                               comment='Last time period was reopened')

    last_reopened_by_user_id = Column(String(36), ForeignKey("users.id"), nullable=True,
                                     comment='User who last reopened the period')

    reopen_count = Column(Integer, default=0,
                         comment='Number of times period has been reopened')

    # Financial summaries (cached after close)
    total_debits = Column(Numeric(19, 4), nullable=True,
                         comment='Total debits in this period')

    total_credits = Column(Numeric(19, 4), nullable=True,
                          comment='Total credits in this period')

    transaction_count = Column(Integer, nullable=True,
                              comment='Number of transactions in period')

    # Workflow integration
    workflow_instance_id = Column(String(36),
                                 ForeignKey("workflow_instances.id"),
                                 nullable=True,
                                 comment='Associated approval workflow instance')

    # Metadata
    notes = Column(Text, nullable=True,
                  comment='Period notes or closing comments')

    closing_checklist = Column(JSON, nullable=True,
                              comment='Period-end checklist items and status')

    # Branch support
    branch_id = Column(String(36), ForeignKey("branches.id"), nullable=True,
                      comment='Branch if using separate periods per branch')

    # Relationships
    fiscal_year = relationship("FiscalYear", back_populates="periods")

    opened_by = relationship("User", foreign_keys=[opened_by_user_id])
    soft_closed_by = relationship("User", foreign_keys=[soft_closed_by_user_id])
    hard_closed_by = relationship("User", foreign_keys=[hard_closed_by_user_id])
    locked_by = relationship("User", foreign_keys=[locked_by_user_id])
    last_reopened_by = relationship("User", foreign_keys=[last_reopened_by_user_id])

    # TODO: Uncomment when WorkflowInstance model is implemented
    # workflow_instance = relationship("WorkflowInstance")
    branch = relationship("Branch")

    # Financial statements generated for this period
    financial_statements = relationship("PeriodEndStatement",
                                       back_populates="fiscal_period",
                                       cascade="all, delete-orphan")

    @validates('start_date', 'end_date')
    def validate_dates(self, key, value):
        """Ensure dates are valid"""
        if key == 'end_date' and hasattr(self, 'start_date') and self.start_date:
            if value <= self.start_date:
                raise ValueError("End date must be after start date")
        return value

    def can_post_transactions(self) -> bool:
        """Check if transactions can be posted to this period"""
        if self.is_locked:
            return False
        return self.status in [PeriodStatus.OPEN, PeriodStatus.SOFT_CLOSED]

    def is_current_period(self) -> bool:
        """Check if this is the current period based on today's date"""
        today = date.today()
        return self.start_date <= today <= self.end_date

    def to_dict(self) -> Dict[str, Any]:
        """Convert to dictionary"""
        return {
            'id': self.id,
            'fiscal_year_id': self.fiscal_year_id,
            'period_name': self.period_name,
            'period_code': self.period_code,
            'period_number': self.period_number,
            'period_type': self.period_type.value if self.period_type else None,
            'start_date': self.start_date.isoformat() if self.start_date else None,
            'end_date': self.end_date.isoformat() if self.end_date else None,
            'status': self.status.value if self.status else None,
            'is_locked': self.is_locked,
            'opened_date': self.opened_date.isoformat() if self.opened_date else None,
            'soft_closed_date': self.soft_closed_date.isoformat() if self.soft_closed_date else None,
            'hard_closed_date': self.hard_closed_date.isoformat() if self.hard_closed_date else None,
            'locked_date': self.locked_date.isoformat() if self.locked_date else None,
            'transaction_count': self.transaction_count,
            'total_debits': float(self.total_debits) if self.total_debits else None,
            'total_credits': float(self.total_credits) if self.total_credits else None,
            'created_at': self.created_at.isoformat() if self.created_at else None,
            'updated_at': self.updated_at.isoformat() if self.updated_at else None,
        }


class PeriodEndStatement(BaseModel):
    """
    Period-end financial statement archival

    Stores snapshot of financial statements at period/year end.
    Provides historical reference and audit trail.
    """
    __tablename__ = "period_end_statements"

    id = Column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))

    # Link to period
    fiscal_period_id = Column(String(36),
                             ForeignKey("fiscal_periods.id", ondelete='CASCADE'),
                             nullable=False,
                             index=True)

    fiscal_year_id = Column(String(36),
                           ForeignKey("fiscal_years.id", ondelete='CASCADE'),
                           nullable=False,
                           index=True)

    # Statement metadata
    statement_type = Column(String(50), nullable=False,
                           comment='Type: trial_balance, income_statement, balance_sheet, cash_flow, etc.')

    statement_name = Column(String(200), nullable=False,
                           comment='Display name of the statement')

    statement_date = Column(Date, nullable=False,
                           comment='Date the statement was generated')

    generated_date = Column(DateTime, nullable=False, default=datetime.utcnow,
                           comment='Timestamp when statement was created')

    generated_by_user_id = Column(String(36), ForeignKey("users.id"), nullable=False,
                                 comment='User who generated the statement')

    # Statement data (JSON format for flexibility)
    statement_data = Column(JSON, nullable=False,
                           comment='Complete statement data in structured JSON format')

    # Rendered formats (optional - for quick retrieval)
    pdf_file_path = Column(String(500), nullable=True,
                          comment='Path to generated PDF file')

    excel_file_path = Column(String(500), nullable=True,
                            comment='Path to generated Excel file')

    # Summary metrics (for quick reference)
    total_assets = Column(Numeric(19, 4), nullable=True,
                         comment='Total assets (for balance sheet)')

    total_liabilities = Column(Numeric(19, 4), nullable=True,
                              comment='Total liabilities (for balance sheet)')

    total_equity = Column(Numeric(19, 4), nullable=True,
                         comment='Total equity (for balance sheet)')

    net_income = Column(Numeric(19, 4), nullable=True,
                       comment='Net income/loss (for income statement)')

    total_revenue = Column(Numeric(19, 4), nullable=True,
                          comment='Total revenue (for income statement)')

    total_expenses = Column(Numeric(19, 4), nullable=True,
                           comment='Total expenses (for income statement)')

    # Audit and compliance
    is_audited = Column(Boolean, default=False,
                       comment='Whether this statement has been audited')

    audited_date = Column(DateTime, nullable=True,
                         comment='When the audit was completed')

    audited_by = Column(String(200), nullable=True,
                       comment='Auditor name/firm')

    audit_notes = Column(Text, nullable=True,
                        comment='Audit notes or qualifications')

    # Metadata
    notes = Column(Text, nullable=True,
                  comment='Additional notes')

    tags = Column(JSON, nullable=True,
                 comment='Tags for categorization')

    # Branch support
    branch_id = Column(String(36), ForeignKey("branches.id"), nullable=True,
                      comment='Branch this statement belongs to')

    # Relationships
    fiscal_period = relationship("FiscalPeriod", back_populates="financial_statements")
    fiscal_year = relationship("FiscalYear")
    generated_by = relationship("User")
    branch = relationship("Branch")

    def to_dict(self) -> Dict[str, Any]:
        """Convert to dictionary"""
        return {
            'id': self.id,
            'fiscal_period_id': self.fiscal_period_id,
            'fiscal_year_id': self.fiscal_year_id,
            'statement_type': self.statement_type,
            'statement_name': self.statement_name,
            'statement_date': self.statement_date.isoformat() if self.statement_date else None,
            'generated_date': self.generated_date.isoformat() if self.generated_date else None,
            'total_assets': float(self.total_assets) if self.total_assets else None,
            'total_liabilities': float(self.total_liabilities) if self.total_liabilities else None,
            'total_equity': float(self.total_equity) if self.total_equity else None,
            'net_income': float(self.net_income) if self.net_income else None,
            'total_revenue': float(self.total_revenue) if self.total_revenue else None,
            'total_expenses': float(self.total_expenses) if self.total_expenses else None,
            'is_audited': self.is_audited,
            'audited_date': self.audited_date.isoformat() if self.audited_date else None,
            'audited_by': self.audited_by,
            'created_at': self.created_at.isoformat() if self.created_at else None,
        }


class PeriodCloseChecklist(BaseModel):
    """
    Period close checklist template and tracking

    Defines tasks that must be completed before closing a period.
    Ensures consistency and compliance with closing procedures.
    """
    __tablename__ = "period_close_checklists"

    id = Column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))

    # Template or instance
    is_template = Column(Boolean, default=True,
                        comment='True if template, False if instance for specific period')

    template_name = Column(String(200), nullable=True,
                          comment='Template name (for templates only)')

    # Link to period (for instances)
    fiscal_period_id = Column(String(36),
                             ForeignKey("fiscal_periods.id", ondelete='CASCADE'),
                             nullable=True,
                             index=True)

    # Checklist items
    checklist_items = Column(JSON, nullable=False,
                            comment='Array of checklist items with status')

    # Example structure:
    # [
    #   {
    #     "task": "Reconcile bank accounts",
    #     "status": "completed",
    #     "completed_by": "user_id",
    #     "completed_date": "2025-01-15T10:30:00",
    #     "required": true,
    #     "order": 1
    #   },
    #   ...
    # ]

    # Progress tracking
    total_items = Column(Integer, nullable=False, default=0)
    completed_items = Column(Integer, nullable=False, default=0)
    completion_percentage = Column(Numeric(5, 2), nullable=False, default=0.0)

    # Status
    is_complete = Column(Boolean, default=False,
                        comment='All required items completed')

    completed_date = Column(DateTime, nullable=True,
                           comment='When all items were completed')

    # Metadata
    notes = Column(Text, nullable=True)

    # Relationships
    fiscal_period = relationship("FiscalPeriod")

    def to_dict(self) -> Dict[str, Any]:
        """Convert to dictionary"""
        return {
            'id': self.id,
            'is_template': self.is_template,
            'template_name': self.template_name,
            'fiscal_period_id': self.fiscal_period_id,
            'checklist_items': self.checklist_items,
            'total_items': self.total_items,
            'completed_items': self.completed_items,
            'completion_percentage': float(self.completion_percentage) if self.completion_percentage else 0.0,
            'is_complete': self.is_complete,
            'created_at': self.created_at.isoformat() if self.created_at else None,
        }
