#!/usr/bin/env python3
from __future__ import annotations

import argparse
import json
from datetime import datetime
from pathlib import Path
from typing import Any, Iterable
import uuid

from .format import ActionEvent, now_iso, to_json, normalize_tags, validate_event, SPEC_VERSION


ROOT = Path.cwd()
STATE_DIR = ROOT / ".areg"
CONFIG_FILE = STATE_DIR / "config.json"
DEFAULT_LOG = STATE_DIR / "actions.jsonl"


def _load_config() -> dict[str, Any]:
    if not CONFIG_FILE.exists():
        return {"log_path": str(DEFAULT_LOG)}
    return json.loads(CONFIG_FILE.read_text())


def _save_config(cfg: dict[str, Any]) -> None:
    STATE_DIR.mkdir(parents=True, exist_ok=True)
    CONFIG_FILE.write_text(json.dumps(cfg, indent=2))


def _log_path(cfg: dict[str, Any]) -> Path:
    return Path(cfg.get("log_path", str(DEFAULT_LOG)))


def init_cmd() -> None:
    cfg = _load_config()
    _save_config(cfg)
    log_path = _log_path(cfg)
    log_path.parent.mkdir(parents=True, exist_ok=True)
    if not log_path.exists():
        log_path.touch()
    print(f"Initialized: {log_path}")


def _parse_time(value: str | None) -> datetime | None:
    if not value:
        return None
    return datetime.fromisoformat(value)


def _read_events(path: Path) -> Iterable[dict[str, Any]]:
    if not path.exists():
        return []
    with path.open() as f:
        for line in f:
            line = line.strip()
            if not line:
                continue
            try:
                yield json.loads(line)
            except json.JSONDecodeError:
                continue


def log_cmd(args: argparse.Namespace) -> None:
    cfg = _load_config()
    log_path = _log_path(cfg)
    log_path.parent.mkdir(parents=True, exist_ok=True)

    tags = normalize_tags(args.tags.split(",") if args.tags else [])
    metadata = json.loads(args.metadata) if args.metadata else {}

    event = ActionEvent(
        id=args.id or str(uuid.uuid4()),
        ts=now_iso(),
        user_id=args.user_id,
        action_type=args.action_type,
        tool=args.tool,
        target=args.target,
        decision=args.decision,
        status=args.status,
        reason=args.reason,
        tags=tags,
        metadata=metadata,
    )
    with log_path.open("a") as f:
        f.write(json.dumps(to_json(event), ensure_ascii=False) + "\n")
    print(f"Logged {event.id}")


def _filter_events(events: Iterable[dict[str, Any]], args: argparse.Namespace) -> list[dict[str, Any]]:
    decision = args.decision
    action_type = args.action_type
    status = args.status
    contains = args.contains
    since = _parse_time(args.since)
    until = _parse_time(args.until)

    result = []
    for e in events:
        if decision and e.get("decision") != decision:
            continue
        if action_type and e.get("action_type") != action_type:
            continue
        if status and e.get("status") != status:
            continue
        if contains:
            hay = json.dumps(e, ensure_ascii=False)
            if contains not in hay:
                continue
        if since or until:
            try:
                ts = datetime.fromisoformat(e.get("ts"))
            except Exception:
                continue
            if since and ts < since:
                continue
            if until and ts > until:
                continue
        result.append(e)
    return result


def view_cmd(args: argparse.Namespace) -> None:
    cfg = _load_config()
    log_path = _log_path(cfg)
    events = _filter_events(_read_events(log_path), args)

    if args.limit:
        events = events[-args.limit :]

    cols = [
        ("ts", 20),
        ("user_id", 10),
        ("action_type", 12),
        ("decision", 10),
        ("status", 10),
        ("tool", 10),
        ("target", 20),
    ]

    header = " ".join(k.ljust(w) for k, w in cols)
    print(header)
    print("-" * len(header))
    for e in events:
        row = []
        for key, width in cols:
            val = str(e.get(key, ""))
            if len(val) > width:
                val = val[: width - 3] + "..."
            row.append(val.ljust(width))
        print(" ".join(row))


def stats_cmd(args: argparse.Namespace) -> None:
    cfg = _load_config()
    log_path = _log_path(cfg)
    events = _filter_events(_read_events(log_path), args)

    by_decision: dict[str, int] = {}
    by_action: dict[str, int] = {}

    for e in events:
        by_decision[e.get("decision") or "unknown"] = by_decision.get(e.get("decision") or "unknown", 0) + 1
        by_action[e.get("action_type") or "unknown"] = by_action.get(e.get("action_type") or "unknown", 0) + 1

    print("Decision counts")
    for k, v in sorted(by_decision.items(), key=lambda x: -x[1]):
        print(f"- {k}: {v}")

    print("Action type counts")
    for k, v in sorted(by_action.items(), key=lambda x: -x[1]):
        print(f"- {k}: {v}")


def export_cmd(args: argparse.Namespace) -> None:
    cfg = _load_config()
    log_path = _log_path(cfg)
    events = _filter_events(_read_events(log_path), args)
    if args.limit:
        events = events[-args.limit :]
    print(json.dumps(events, ensure_ascii=False, indent=2))


def validate_cmd(args: argparse.Namespace) -> None:
    cfg = _load_config()
    log_path = _log_path(cfg)
    bad = 0
    total = 0
    for e in _read_events(log_path):
        total += 1
        ok, reason = validate_event(e)
        if not ok:
            bad += 1
            print(f"Invalid: {reason} -> {e}")
    if bad == 0:
        print(f"All good: {total} events (spec {SPEC_VERSION})")
    else:
        print(f"Invalid events: {bad}/{total}")


def main() -> None:
    parser = argparse.ArgumentParser(prog="areg")
    sub = parser.add_subparsers(dest="cmd")

    sub.add_parser("init")

    log_p = sub.add_parser("log")
    log_p.add_argument("--id")
    log_p.add_argument("--user-id", required=True)
    log_p.add_argument("--action-type", required=True)
    log_p.add_argument("--tool")
    log_p.add_argument("--target")
    log_p.add_argument("--decision")
    log_p.add_argument("--status")
    log_p.add_argument("--reason")
    log_p.add_argument("--tags")
    log_p.add_argument("--metadata")

    view_p = sub.add_parser("view")
    view_p.add_argument("--decision")
    view_p.add_argument("--action-type")
    view_p.add_argument("--status")
    view_p.add_argument("--contains")
    view_p.add_argument("--since")
    view_p.add_argument("--until")
    view_p.add_argument("--limit", type=int)

    stats_p = sub.add_parser("stats")
    stats_p.add_argument("--decision")
    stats_p.add_argument("--action-type")
    stats_p.add_argument("--status")
    stats_p.add_argument("--contains")
    stats_p.add_argument("--since")
    stats_p.add_argument("--until")

    export_p = sub.add_parser("export")
    export_p.add_argument("--decision")
    export_p.add_argument("--action-type")
    export_p.add_argument("--status")
    export_p.add_argument("--contains")
    export_p.add_argument("--since")
    export_p.add_argument("--until")
    export_p.add_argument("--limit", type=int)

    sub.add_parser("validate")

    args = parser.parse_args()

    if args.cmd == "init":
        init_cmd()
    elif args.cmd == "log":
        log_cmd(args)
    elif args.cmd == "view":
        view_cmd(args)
    elif args.cmd == "stats":
        stats_cmd(args)
    elif args.cmd == "export":
        export_cmd(args)
    elif args.cmd == "validate":
        validate_cmd(args)
    else:
        parser.print_help()


if __name__ == "__main__":
    main()
