from __future__ import annotations

import argparse
import csv
import json
import sqlite3
import sys
from datetime import datetime
from pathlib import Path
from typing import Any, Dict, List

from store_db.csv_safe import csv_safe_row
from store_db.db import connect, get_db_path, list_tables
from store_db.operation_log import write_operation_log
from store_db.response import emit_error, emit_ok
from store_db.schema_v1 import REQUIRED_TABLES, SEED_DATA_TABLES
from store_db.seed import STARTER_DIR, apply_default_seed

META_WHITELIST = {
    "segment_preset",
    "lexicon",
    "balance_low_threshold",
    "card_expiring_days",
    "member_due_days",
    "starter_applied",
}


def cmd_migrate(args: argparse.Namespace) -> int:
    conn = connect()
    tables = list_tables(conn)
    write_operation_log(
        conn,
        session_id=getattr(args, "session_id", None),
        skill="store-suite",
        command="migrate",
        op="update",
        table_name="meta_schema_version",
        entity_id="1",
        request_json={},
        before_json=None,
        after_json={"tables": tables},
        result="ok",
        error_code=None,
        confirmed=False,
        duration_ms=0,
    )
    conn.commit()
    emit_ok({"db_path": str(get_db_path()), "schema_version": 1, "tables": tables})
    return 0


def cmd_seed(args: argparse.Namespace) -> int:
    conn = connect()
    if not STARTER_DIR.is_dir():
        emit_error("not_found", {"path": str(STARTER_DIR)})

    try:
        apply_default_seed(conn, with_field_defs=not args.no_field_defs)
    except FileNotFoundError as e:
        emit_error("not_found", {"detail": str(e)})
    write_operation_log(
        conn,
        session_id=getattr(args, "session_id", None),
        skill="store-suite",
        command="seed",
        op="create",
        table_name="store_meta",
        entity_id="default",
        request_json={"with_field_defs": not args.no_field_defs},
        before_json=None,
        after_json={"starter": "default"},
        result="ok",
        error_code=None,
        confirmed=True,
        duration_ms=0,
    )
    conn.commit()
    emit_ok({"starter": "default", "db_path": str(get_db_path())})
    return 0


def cmd_verify_seed(args: argparse.Namespace) -> int:
    conn = connect()
    checks: Dict[str, bool] = {}
    all_tables = set(list_tables(conn))
    for table in REQUIRED_TABLES:
        checks[f"table:{table}"] = table in all_tables
    for table in SEED_DATA_TABLES:
        n = conn.execute(f"SELECT COUNT(*) AS c FROM {table}").fetchone()["c"]
        checks[f"data:{table}"] = n > 0

    col = conn.execute("PRAGMA table_info(store_members)").fetchall()
    col_names = {r["name"] for r in col}
    checks["birthday_column"] = "birthday" in col_names

    ok = all(checks.values())
    if not ok:
        emit_error("verify_failed", {"checks": checks}, exit_code=1)
    emit_ok({"checks": checks})
    return 0


def cmd_operation_log_query(args: argparse.Namespace) -> int:
    conn = connect()
    sql = "SELECT * FROM store_operation_log WHERE 1=1"
    params: List[Any] = []
    if args.table:
        sql += " AND table_name = ?"
        params.append(args.table)
    if args.op:
        sql += " AND op = ?"
        params.append(args.op)
    if args.date_from:
        sql += " AND occurred_at >= ?"
        params.append(args.date_from)
    if args.date_to:
        sql += " AND occurred_at <= ?"
        params.append(args.date_to)
    sql += " ORDER BY occurred_at DESC LIMIT ?"
    params.append(args.limit)

    rows = [dict(r) for r in conn.execute(sql, params).fetchall()]
    write_operation_log(
        conn,
        session_id=getattr(args, "session_id", None),
        skill="store-suite",
        command="operation-log-query",
        op="read",
        table_name="store_operation_log",
        entity_id=None,
        request_json=_args_json(args),
        before_json=None,
        after_json={"count": len(rows)},
        result="ok",
        error_code=None,
        confirmed=False,
        duration_ms=0,
    )
    conn.commit()
    emit_ok({"items": rows, "count": len(rows)})
    return 0


def cmd_operation_log_export(args: argparse.Namespace) -> int:
    conn = connect()
    sql = "SELECT * FROM store_operation_log WHERE occurred_at >= ? AND occurred_at <= ? ORDER BY occurred_at"
    rows = conn.execute(sql, (args.date_from, args.date_to)).fetchall()
    out = Path(args.path)
    out.parent.mkdir(parents=True, exist_ok=True)
    if rows:
        fieldnames = rows[0].keys()
        with out.open("w", encoding="utf-8-sig", newline="") as f:
            w = csv.DictWriter(f, fieldnames=fieldnames)
            w.writeheader()
            for r in rows:
                w.writerow(csv_safe_row(dict(r)))
    else:
        out.write_text("", encoding="utf-8")

    write_operation_log(
        conn,
        session_id=getattr(args, "session_id", None),
        skill="store-suite",
        command="operation-log-export",
        op="read",
        table_name="store_operation_log",
        entity_id=None,
        request_json=_args_json(args),
        before_json=None,
        after_json={"path": str(out), "rows": len(rows)},
        result="ok",
        error_code=None,
        confirmed=False,
        duration_ms=0,
    )
    conn.commit()
    emit_ok({"path": str(out), "rows": len(rows)})
    return 0


def cmd_store_meta_set(args: argparse.Namespace) -> int:
    if args.key not in META_WHITELIST:
        emit_error("invalid_meta_key", {"key": args.key, "allowed": sorted(META_WHITELIST)})
    conn = connect()
    conn.execute(
        "INSERT OR REPLACE INTO store_meta(key, value_json) VALUES (?, ?)",
        (args.key, args.value if args.value.startswith("{") or args.value.startswith("[") else json.dumps(args.value)),
    )
    conn.commit()
    emit_ok({"key": args.key, "value_json": args.value})
    return 0


def _args_json(args: argparse.Namespace) -> Dict[str, Any]:
    skip = {"func", "meta_cmd"}
    out = {}
    for k, v in vars(args).items():
        if k.startswith("_") or k in skip:
            continue
        if callable(v):
            continue
        out[k] = v
    return out


def cmd_db_path(args: argparse.Namespace) -> int:
    emit_ok({"db_path": str(get_db_path())})
    return 0


def build_parser() -> argparse.ArgumentParser:
    p = argparse.ArgumentParser(description="Store suite kernel CLI")
    p.add_argument("--session-id", default=None, help="Agent session id for audit log")
    sub = p.add_subparsers(dest="cmd", required=True)

    s = sub.add_parser("migrate", help="(Optional) Ensure schema; normally automatic on first CLI")
    s.set_defaults(func=cmd_migrate)

    s = sub.add_parser("db-path", help="Print resolved database path")
    s.set_defaults(func=cmd_db_path)

    s = sub.add_parser("seed", help="Load default starter sample data (idempotent INSERT OR IGNORE)")
    s.add_argument(
        "--no-field-defs",
        action="store_true",
        help="Skip field_defs_seed.json (auto-seed always includes field defs)",
    )
    s.set_defaults(func=cmd_seed)

    s = sub.add_parser("verify-seed", help="Verify starter data loaded")
    s.set_defaults(func=cmd_verify_seed)

    s = sub.add_parser("operation-log-query")
    s.add_argument("--table", default=None)
    s.add_argument("--op", default=None)
    s.add_argument("--from", dest="date_from", default=None)
    s.add_argument("--to", dest="date_to", default=None)
    s.add_argument("--limit", type=int, default=50)
    s.set_defaults(func=cmd_operation_log_query)

    s = sub.add_parser("operation-log-export")
    s.add_argument("--from", dest="date_from", required=True)
    s.add_argument("--to", dest="date_to", required=True)
    s.add_argument("--path", required=True)
    s.set_defaults(func=cmd_operation_log_export)

    s = sub.add_parser("store-meta")
    meta_sub = s.add_subparsers(dest="meta_cmd", required=True)
    m = meta_sub.add_parser("set")
    m.add_argument("--key", required=True)
    m.add_argument("--value", required=True)
    m.set_defaults(func=cmd_store_meta_set)

    return p


def main() -> int:
    parser = build_parser()
    args = parser.parse_args()
    fn = getattr(args, "func", None)
    if fn is None:
        parser.print_help()
        return 2
    return int(fn(args))


if __name__ == "__main__":
    sys.exit(main())
