"""Lifecycle conturi ROAAUTO (admin, fara suprafata HTTP).

Functii pure de creare/listare/(de)activare cont, partajate intre CLI
(`tools/account.py`, Etapa 3.1) si fluxul web de self-onboarding (Etapa 3.3,
care reuseaza `create_account` + `active`). Identitatea de login (email/parola)
NU traieste aici — apartine 3.3.

NOTA lifecycle `active`: coloana `accounts.active` este un flag de lifecycle
consumat de 3.3 (gate „cont in asteptare", `active=0`). Pana la gate-ul worker din
3.3, `active=0` NU opreste trimiterile (worker-ul nu citeste contul, doar
`api_keys.active`). `deactivate` marcheaza intentia administrativa; nu blocheaza
inca fluxul de trimitere. (Addendum A2.)
"""

from __future__ import annotations

import sqlite3
from datetime import datetime, timedelta, timezone


def _norm_cui(cui: str | None) -> str | None:
    """trim + upper; sir gol -> ValueError daca e string gol, None daca e None."""
    if cui is None:
        return None
    cui = cui.strip().upper()
    if cui == "":
        raise ValueError("CUI gol (un CUI trebuie sa fie un sir nevid)")
    return cui


def _norm_email(email: str | None) -> str | None:
    """trim + lower; sir gol -> ValueError daca e string gol, None daca e None."""
    if email is None:
        return None
    email = email.strip().lower()
    if email == "":
        raise ValueError("email gol (un email trebuie sa fie un sir nevid)")
    return email


def create_account(
    conn: sqlite3.Connection,
    name: str,
    cui: str | None = None,
    email: str | None = None,
    active: bool = True,
    requested_plan: str | None = None,
    consent_at: str | None = None,
) -> int:
    """Insereaza un cont si intoarce id-ul nou (AUTOINCREMENT, deci >=2 — nu atinge default id=1).

    `name` gol/whitespace -> ValueError. `cui` se normalizeaza (trim+upper); sir gol -> ValueError.
    `email` se normalizeaza (trim+lower); sir gol -> ValueError.
    Un CUI deja folosit -> ValueError cu cauza+fix. Unicitatea e impusa de indexul partial
    `ux_accounts_cui` (nu de un check separat), deci e sigura la concurenta.

    `requested_plan`: planul CERUT la signup (separat de `tier`). NU acorda drepturi — `tier`
    ramane mereu 'free' la creare; planul cerut e doar o intentie pentru integrarea platilor.
    Valoare invalida (nu e in VALID_TIERS) -> ignorata (stocata NULL), nu arunca.
    `consent_at`: marca temporala consimtamant Termeni+GDPR (proba); None = fara flux consimtamant.
    """
    name = (name or "").strip()
    if not name:
        raise ValueError("name gol (un cont are nevoie de nume)")
    cui = _norm_cui(cui)
    email = _norm_email(email)
    # Planul cerut: pastram doar valori valide; orice altceva -> NULL (defensiv).
    req_plan = requested_plan if requested_plan in VALID_TIERS else None
    try:
        # Trial Pro automat la creare (PRD 5.17 US-001): tier='free' + trial_until=now+30z.
        trial_until = (
            (datetime.now(timezone.utc) + timedelta(days=30)).strftime("%Y-%m-%d %H:%M:%S")
        )
        # Invariant (5.5): active=1 <=> status='active'; cont creat inactiv = 'pending'.
        cur = conn.execute(
            "INSERT INTO accounts (name, cui, email, active, status, tier, trial_until, "
            "requested_plan, consent_at) "
            "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
            (name, cui, email, 1 if active else 0, "active" if active else "pending",
             "free", trial_until, req_plan, consent_at),
        )
    except sqlite3.IntegrityError:
        existing = conn.execute("SELECT id FROM accounts WHERE cui=?", (cui,)).fetchone()
        owner = existing["id"] if existing else "?"
        raise ValueError(
            f"CUI {cui} e deja folosit de contul {owner} "
            f"(foloseste 'activate --account {owner}' sau alt CUI)"
        )
    return int(cur.lastrowid or 0)


def account_is_complete(row: sqlite3.Row | dict) -> bool:
    """Returneaza True daca contul are companie (name), email si CUI ne-goale.

    Contul de sistem id=1 (default) este EXCEPTAT si returneaza intotdeauna True
    (nu are sens sa-l marcam ca incomplet — nu e un cont de client).
    """
    acct_id = row["id"] if "id" in row.keys() else None
    if acct_id == 1:
        return True
    name = (row["name"] or "").strip()
    cui = (row["cui"] or "").strip()
    email_val = (row["email"] or "").strip() if "email" in row.keys() else ""
    return bool(name and cui and email_val)


def set_active(conn: sqlite3.Connection, account_id: int, active: bool) -> None:
    """Comuta `accounts.active`. Idempotent (set activ pe activ nu arunca).
    Cont inexistent -> ValueError.

    Mentine invariantul 5.5 active=1 <=> status='active': activarea -> 'active',
    dezactivarea -> 'pending' (legacy „in asteptare"). Pentru blocare/arhivare/stergere
    foloseste `set_status`/`delete_account`.
    """
    row = conn.execute("SELECT 1 FROM accounts WHERE id=?", (account_id,)).fetchone()
    if not row:
        raise ValueError(f"cont inexistent: {account_id}")
    conn.execute(
        "UPDATE accounts SET active=?, status=? WHERE id=?",
        (1 if active else 0, "active" if active else "pending", account_id),
    )


# Stari de ciclu de viata gestionate explicit (5.5). 'deleted' = stergere soft (purjata de
# retentie); restul sunt reversibile.
VALID_STATUSES = ("pending", "active", "blocked", "archived", "deleted")
# Tieruri de cont valide (5.17). Sursa de adevar: app/plans.py#PLANS (nu duplica valorile).
VALID_TIERS = ("free", "standard", "pro", "premium")
# Verbele care nu se pot aplica contului de sistem id=1 (protejat, ca la deactivate in 3.3b).
_PROTECTED_ACCOUNT_ID = 1


def set_status(conn: sqlite3.Connection, account_id: int, status: str) -> None:
    """Seteaza `accounts.status` la una din `VALID_STATUSES`, mentinand mirror-ul `active`
    (active=1 doar pentru 'active', altfel 0).

    Contul de sistem id=1 NU poate fi mutat din 'active' (cont default) -> ValueError.
    Status invalid sau cont inexistent -> ValueError.
    """
    if status not in VALID_STATUSES:
        raise ValueError(f"status invalid: {status}")
    row = conn.execute("SELECT 1 FROM accounts WHERE id=?", (account_id,)).fetchone()
    if not row:
        raise ValueError(f"cont inexistent: {account_id}")
    if account_id == _PROTECTED_ACCOUNT_ID and status != "active":
        raise ValueError("Contul default (id=1) nu poate fi blocat/arhivat/sters (cont de sistem).")
    conn.execute(
        "UPDATE accounts SET active=?, status=? WHERE id=?",
        (1 if status == "active" else 0, status, account_id),
    )


def get_auto_send(conn: sqlite3.Connection, account_id: int) -> bool:
    """Comutatorul Auto per cont (PRD 5.19 US-001): `accounts.auto_send_enabled` ca bool.

    Cont inexistent -> False (Auto OFF: sigur, tine randurile). Nu comita (ca set_active).
    """
    row = conn.execute(
        "SELECT auto_send_enabled FROM accounts WHERE id=?", (account_id,)
    ).fetchone()
    if not row:
        return False
    return bool(row["auto_send_enabled"])


def set_auto_send(conn: sqlite3.Connection, account_id: int, enabled: bool) -> None:
    """Comuta `accounts.auto_send_enabled` (PRD 5.19 US-001). Idempotent (set pe aceeasi
    valoare nu arunca). Scoped pe cont. Nu comita — respecta conventia fisierului (apelantul
    comita, ca la set_active/set_status). Cont inexistent -> UPDATE fara efect (no-op sigur).
    """
    conn.execute(
        "UPDATE accounts SET auto_send_enabled=? WHERE id=?",
        (1 if enabled else 0, account_id),
    )


def held_for_account(conn: sqlite3.Connection, account_id: int) -> int:
    """Snapshot-ul `held` la ingestie (PRD 5.19 US-001). SURSA UNICA (chokepoint) folosita de
    TOATE situ-rile de ingestie care scriu `status='queued'` (US-002).

    Intoarce 0 (nu tine) daca `accounts.auto_send_enabled=1`, altfel 1 (tine randul). Cont
    inexistent -> 1 (sigur: tine randul, nu-l trimite fara comutator explicit).
    """
    return 0 if get_auto_send(conn, account_id) else 1


def set_tier(
    conn: sqlite3.Connection,
    account_id: int,
    tier: str,
    trial_until: str | None = None,
) -> None:
    """Seteaza planul unui cont (tier + trial_until).

    tier invalid -> ValueError cu mesaj clar.
    Contul de sistem id=1 e protejat (ca set_status).
    Cont inexistent -> ValueError.
    Logheaza schimbarea in app_events (reuse observ.log_event, fara PII nou).

    trial_until: string ISO UTC ("YYYY-MM-DD HH:MM:SS") sau None (sterge trial-ul).
    """
    if tier not in VALID_TIERS:
        raise ValueError(
            f"tier invalid: {tier!r} (valid: {', '.join(VALID_TIERS)})"
        )
    row = conn.execute("SELECT 1 FROM accounts WHERE id=?", (account_id,)).fetchone()
    if not row:
        raise ValueError(f"cont inexistent: {account_id}")
    if account_id == _PROTECTED_ACCOUNT_ID:
        raise ValueError(
            "Contul default (id=1) nu poate fi mutat pe alt plan via CLI "
            "(cont de sistem, tratat coerent)."
        )
    conn.execute(
        "UPDATE accounts SET tier=?, trial_until=? WHERE id=?",
        (tier, trial_until, account_id),
    )
    # Audit in app_events (decizie PRD 5.17 US-008, fara PII nou)
    try:
        from .observ import log_event
        log_event(
            "plan_schimbare_tier",
            account_id=account_id,
            mesaj=f"tier -> {tier}",
            context={"tier": tier, "trial_until": trial_until},
            conn=conn,
        )
    except Exception:  # noqa: BLE001 — jurnal best-effort (ca observ.log_event)
        pass


def set_trial(conn: sqlite3.Connection, account_id: int, trial_until: str | None) -> None:
    """Seteaza DOAR `trial_until` (acorda/prelungeste/sterge trial Pro), fara a atinge `tier`.

    Trial Pro activ (trial_until in viitor) ridica planul efectiv la 'pro' (vezi
    plans.effective_tier), indiferent de tier-ul de baza. Folosit din panoul admin ca sa
    acorzi un trial fara a schimba tier-ul de baza (post-trial).

    Contul de sistem id=1 e protejat. Cont inexistent -> ValueError.
    trial_until: string ISO UTC ("YYYY-MM-DD HH:MM:SS") sau None (sterge trial-ul).
    """
    row = conn.execute("SELECT 1 FROM accounts WHERE id=?", (account_id,)).fetchone()
    if not row:
        raise ValueError(f"cont inexistent: {account_id}")
    if account_id == _PROTECTED_ACCOUNT_ID:
        raise ValueError("Contul default (id=1) nu poate primi trial (cont de sistem).")
    conn.execute(
        "UPDATE accounts SET trial_until=? WHERE id=?", (trial_until, account_id)
    )
    # Audit in app_events (best-effort, fara PII nou — ca set_tier).
    try:
        from .observ import log_event
        log_event(
            "plan_trial_setat",
            account_id=account_id,
            mesaj=f"trial_until -> {trial_until or 'NULL'}",
            context={"trial_until": trial_until},
            conn=conn,
        )
    except Exception:  # noqa: BLE001 — jurnal best-effort (ca observ.log_event)
        pass


def delete_account(conn: sqlite3.Connection, account_id: int) -> None:
    """Stergere SOFT: randul ramane ca tombstone (status='deleted', scos din liste), DAR datele
    sensibile se purjeaza IMEDIAT (GDPR/L.142): credentialele RAR criptate sterse, cheile API
    revocate si CUI-ul eliberat (ca acelasi CUI sa se poata re-inregistra — altfel indexul unic
    `ux_accounts_cui` l-ar tine blocat de un cont invizibil). Contul de sistem id=1 e protejat.

    Nota: nu facem hard DELETE pe rand din cauza FK-urilor (submissions/api_keys/...); pastram
    tombstone-ul pentru audit, dar fara PII. Jobul de retentie T16 purjeaza `submissions`/batches,
    NU acest tombstone — de aceea purjam PII aici, la momentul stergerii."""
    set_status(conn, account_id, "deleted")  # valideaza existenta + protejeaza id=1; seteaza status+active=0
    conn.execute(
        "UPDATE accounts SET rar_creds_test_enc=NULL, rar_creds_prod_enc=NULL, cui=NULL WHERE id=?",
        (account_id,),
    )
    conn.execute(
        "UPDATE api_keys SET active=0, revoked_at=datetime('now') WHERE account_id=? AND active=1",
        (account_id,),
    )


def list_accounts(conn: sqlite3.Connection) -> list[dict]:
    """Metadate conturi (FARA creds RAR criptate), ordonate dupa id. Exclude conturile 'deleted'
    (stergere soft -> invizibile in panou)."""
    rows = conn.execute(
        "SELECT id, name, cui, email, active, status, tier, trial_until, "
        "requested_plan, consent_at, created_at FROM accounts "
        "WHERE status != 'deleted' ORDER BY id"
    ).fetchall()
    return [dict(r) for r in rows]
