"""
Period Helper - Shared fiscal year and period calculation utilities.

FY Labeling Convention (CRITICAL):
    FY label = calendar year in which the fiscal year ENDS (standard finance convention)

    Examples:
    - first_fy=26, fy_start_month=1 (Jan) => FY26 = 2026-01-01 to 2026-12-31
    - first_fy=26, fy_start_month=2 (Feb) => FY26 = 2025-02-01 to 2026-01-31
    - first_fy=26, fy_start_month=7 (Jul) => FY26 = 2025-07-01 to 2026-06-30
"""

from dataclasses import dataclass
from datetime import date
from typing import List, Dict, Literal
from dateutil.relativedelta import relativedelta


@dataclass
class PeriodConfig:
    """Configuration for generating period tables."""
    first_fy: int  # e.g., 26 for FY26
    fy_start_month: int  # 1-12, month when fiscal year starts
    granularity: Literal["quarterly", "monthly"] = "quarterly"
    horizon_periods: int = 4  # Number of periods (4 quarters = 1 FY, 8 = 2 FYs)


@dataclass
class Period:
    """A single fiscal period with label and date boundaries."""
    label: str  # e.g., "FY26 Q1" or "FY26 M01"
    fy: int  # Fiscal year number (e.g., 26)
    period_num: int  # Quarter (1-4) or Month (1-12)
    start_date: date
    end_date: date


def compute_fy_start_year(first_fy: int, fy_start_month: int) -> int:
    """
    Compute the calendar year when a fiscal year starts.

    FY label = calendar year in which FY ENDS.
    Therefore:
        - If fy_start_month == 1, FY starts and ends in same calendar year
        - If fy_start_month > 1, FY starts in prior calendar year

    Args:
        first_fy: The fiscal year number (e.g., 26 for FY26)
        fy_start_month: Month when fiscal year starts (1-12)

    Returns:
        Calendar year when this fiscal year starts
    """
    fy_end_year = 2000 + first_fy
    if fy_start_month == 1:
        return fy_end_year
    else:
        return fy_end_year - 1


def compute_fy_end_year(first_fy: int) -> int:
    """Compute the calendar year when a fiscal year ends."""
    return 2000 + first_fy


def get_quarter_start_month(fy_start_month: int, quarter: int) -> int:
    """
    Get the calendar month when a fiscal quarter starts.

    Args:
        fy_start_month: Month when fiscal year starts (1-12)
        quarter: Quarter number (1-4)

    Returns:
        Calendar month (1-12) when the quarter starts
    """
    # Quarter 1 starts at fy_start_month
    # Each subsequent quarter adds 3 months
    month = fy_start_month + (quarter - 1) * 3
    if month > 12:
        month -= 12
    return month


def build_period_table(config: PeriodConfig) -> List[Period]:
    """
    Build a list of fiscal periods with labels and date boundaries.

    This function generates period data that Python generators write directly
    to the Calc tab as values (not complex sheet formulas).

    Args:
        config: PeriodConfig with FY settings and horizon

    Returns:
        List of Period objects with labels and dates
    """
    periods: List[Period] = []

    # Calculate starting FY's start date
    fy_start_year = compute_fy_start_year(config.first_fy, config.fy_start_month)

    if config.granularity == "quarterly":
        periods_per_fy = 4
        months_per_period = 3
    else:  # monthly
        periods_per_fy = 12
        months_per_period = 1

    current_fy = config.first_fy
    current_period = 1  # Quarter or month within FY

    # Start date of first period
    current_start = date(fy_start_year, config.fy_start_month, 1)

    for i in range(config.horizon_periods):
        # Calculate end date (last day of period)
        period_end = current_start + relativedelta(months=months_per_period) - relativedelta(days=1)

        # Generate label
        if config.granularity == "quarterly":
            label = f"FY{current_fy} Q{current_period}"
        else:
            label = f"FY{current_fy} M{current_period:02d}"

        periods.append(Period(
            label=label,
            fy=current_fy,
            period_num=current_period,
            start_date=current_start,
            end_date=period_end,
        ))

        # Move to next period
        current_start = period_end + relativedelta(days=1)
        current_period += 1

        # Roll over to next FY if needed
        if current_period > periods_per_fy:
            current_period = 1
            current_fy += 1

    return periods


def build_period_table_dicts(config: PeriodConfig) -> List[Dict]:
    """
    Build period table as list of dicts for easy serialization.

    This is the format that generators use to write into Calc tabs.

    Args:
        config: PeriodConfig with FY settings and horizon

    Returns:
        List of dicts with label, start_date, end_date as ISO strings
    """
    periods = build_period_table(config)
    return [
        {
            "label": p.label,
            "fy": p.fy,
            "period_num": p.period_num,
            "start_date": p.start_date.isoformat(),
            "end_date": p.end_date.isoformat(),
        }
        for p in periods
    ]


def get_period_labels(config: PeriodConfig) -> List[str]:
    """Get just the period labels for use as column headers."""
    periods = build_period_table(config)
    return [p.label for p in periods]


def get_quarter_month_labels(fy_start_month: int) -> List[str]:
    """
    Get descriptive month labels for each quarter.

    Args:
        fy_start_month: Month when fiscal year starts (1-12)

    Returns:
        List of 4 strings like "(Jan-Mar)", "(Apr-Jun)", etc.
    """
    month_abbr = ["Jan", "Feb", "Mar", "Apr", "May", "Jun",
                  "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]

    labels = []
    for q in range(4):
        start_month_idx = (fy_start_month - 1 + q * 3) % 12
        end_month_idx = (start_month_idx + 2) % 12
        labels.append(f"({month_abbr[start_month_idx]}-{month_abbr[end_month_idx]})")

    return labels


def compute_next_fy_target(annual_target: float, yoy_growth_pct: float, years_ahead: int = 1) -> float:
    """
    Compute a future FY target using compound growth.

    Args:
        annual_target: Base fiscal year annual target
        yoy_growth_pct: Year-over-year growth as decimal (e.g., 0.15 for 15%)
        years_ahead: Number of fiscal years into the future (default 1)

    Returns:
        Future FY annual target
    """
    return annual_target * (1 + yoy_growth_pct) ** years_ahead


# Backward-compatible alias
compute_fy27_target = compute_next_fy_target
