"""
CA Automator — country-neutral Financial Period engine.

Nepal's fiscal years live in `fiscal_year` (Bikram Sambat-native — genuinely
the right shape for Nepal, predates this module, unchanged by it) and are
still looked up exactly as before: db.current_fy_label(), nepal_calendar.
fy_label_for_ad(), the fiscal_year table directly. Every other country's
fiscal years live in `financial_period` (AD-only, see schema.sql's comment
on that table for why it's a separate table rather than a migration of
fiscal_year) and are looked up here.

Nothing in this module ever falls back to Bikram Sambat math for a
non-Nepal country — that was the actual bug this module exists to close
(see PROJECT_STATE.md). The fallback is always a plain AD calendar year.
"""
from datetime import date, datetime, timedelta, timezone

import db

# Generation rules for countries with a genuine, well-known fiscal-year
# convention. A country with no entry here has no generator — its
# financial_period rows are entered by hand from the admin screen, the same
# way Nepal's fiscal_year rows already are.
GENERATION_RULES = {
    "IN": {"start_month": 4, "start_day": 1, "label": lambda y: f"{y}-{str(y + 1)[-2:]}"},  # 2025-26, Apr-Mar
    "AE": {"start_month": 1, "start_day": 1, "label": lambda y: f"{y}"},                     # calendar year
}


def generate_periods(country_code, start_year, count=5):
    """Returns `count` consecutive (label, start_ad, end_ad) tuples starting
    from start_year, using that country's known convention. Pure — does not
    insert anything; callers decide (see seed_default_periods below, or the
    admin "generate" endpoint)."""
    rule = GENERATION_RULES.get(country_code)
    if not rule:
        raise ValueError(f"No fiscal-year generation rule for country_code {country_code!r} — "
                          f"enter financial_period rows by hand for this country")
    out = []
    for i in range(count):
        y = start_year + i
        start = date(y, rule["start_month"], rule["start_day"])
        end = date(y + 1, rule["start_month"], rule["start_day"]) - timedelta(days=1)
        out.append((rule["label"](y), start.isoformat(), end.isoformat()))
    return out


def seed_default_periods(country_code, around_year=None, back=2, forward=3):
    """Idempotently ensures financial_period has a reasonable spread of
    years around today for a country with a known generation rule —
    `back` years before and `forward` years after. INSERT OR IGNORE against
    the table's own UNIQUE(country_code, label), so re-running (e.g. every
    year) only ever adds the newly-needed year, never duplicates."""
    if around_year is None:
        around_year = datetime.now(timezone.utc).year
    for label, start_ad, end_ad in generate_periods(country_code, around_year - back, back + forward + 1):
        db.execute(
            "INSERT OR IGNORE INTO financial_period (country_code, label, start_ad, end_ad) VALUES (?,?,?,?)",
            (country_code, label, start_ad, end_ad))
    sync_current_period(country_code)


def sync_current_period(country_code):
    """Same convention as db._sync_current_fiscal_year — recomputed on
    demand rather than trusting a flag someone has to remember to flip."""
    today = datetime.now(timezone.utc).strftime("%Y-%m-%d")
    row = db.query(
        "SELECT id FROM financial_period WHERE country_code = ? AND start_ad <= ? AND end_ad >= ? LIMIT 1",
        (country_code, today, today), one=True)
    db.execute("UPDATE financial_period SET is_current = 0 WHERE country_code = ?", (country_code,))
    if row:
        db.execute("UPDATE financial_period SET is_current = 1 WHERE id = ?", (row["id"],))


def periods_for_country(country_code):
    """Rows shaped {label, start_ad, end_ad} for a country — the same shape
    engines.py's grouping helpers already expect, just keyed 'label' instead
    of fiscal_year's Nepal-specific 'label_bs'."""
    return db.query("SELECT label, start_ad, end_ad FROM financial_period WHERE country_code = ? ORDER BY start_ad",
                    (country_code,))


def ensure_periods_exist(country_code):
    """Auto-seeds financial_period the first time a country with a known
    generation rule is actually needed (a firm switches to it, or its admin
    opens the Financial Periods screen) — so periods_for_country() is never
    silently empty for IN/AE just because nobody has visited that screen
    yet. A country with no GENERATION_RULES entry is left alone; its periods
    are hand-entered, same as Nepal's fiscal_year rows always have been."""
    if country_code not in GENERATION_RULES:
        return
    existing = db.query("SELECT COUNT(*) n FROM financial_period WHERE country_code = ?",
                        (country_code,), one=True)["n"]
    if not existing:
        seed_default_periods(country_code)


def financial_periods_for_firm(firm_id):
    """The country-appropriate list of selectable periods for this firm, in
    the SAME {label_bs, start_ad, end_ad, is_current} shape as Nepal's own
    fiscal_year table. label_bs is a legacy name at this point (it predates
    this module) — every existing consumer of this shape, both server-side
    (engines.fixed_assets' day-pro-ration window lookup) and the offline JS
    mirror (web/engines.js's faFyWindow/fyLabelForAd), already keys off
    label_bs regardless of what it actually contains, so reusing the same
    key here — rather than financial_period's own 'label' column name —
    means bootstrap()'s financial_periods field slots into every existing FY
    picker and pro-ration lookup with no further code change, just a
    different (correctly country-scoped) source for the rows."""
    import country_service
    country_code = country_service.firm_country_code(firm_id)
    if country_code == "NP":
        return db.query("SELECT * FROM fiscal_year ORDER BY label_bs")
    ensure_periods_exist(country_code)
    return db.query(
        "SELECT label AS label_bs, start_ad, end_ad, is_current FROM financial_period"
        " WHERE country_code = ? ORDER BY start_ad", (country_code,))


def period_label_for_ad(country_code, ad_date, periods=None):
    """Country-neutral equivalent of nepal_calendar.fy_label_for_ad, for any
    country OTHER than Nepal — Nepal keeps using that function directly,
    unchanged. periods: rows from periods_for_country(); pass None to
    always use the fallback."""
    d = ad_date if isinstance(ad_date, date) else date.fromisoformat(str(ad_date)[:10])
    for p in (periods or []):
        try:
            if p["start_ad"] and p["end_ad"] and p["start_ad"] <= d.isoformat() <= p["end_ad"]:
                return p["label"]
        except (TypeError, ValueError):
            continue
    # No configured period covers this date — a plain AD calendar year.
    # Deliberately never Bikram Sambat math; that was the actual bug.
    return str(d.year)


def current_period_label(firm_id):
    """The one function to call for "what fiscal/financial period is it
    right now for this firm" — resolves the firm's own country first,
    unlike db.current_fy_label() (which has no firm concept and always
    assumes Nepal). Nepal firms get db.current_fy_label()'s existing
    behaviour completely unchanged."""
    import country_service
    country_code = country_service.firm_country_code(firm_id)
    if country_code == "NP":
        return db.current_fy_label()
    return period_label_for_ad(country_code, datetime.now(timezone.utc).date(), periods_for_country(country_code))
