"""Trace view handlers extracted from `hiveos.observe`."""

from __future__ import annotations

import json
import os
import time

from hiveos_trace import terminal_style as _term


def _print_wrapped_kv(key, value, *, indent="  "):
    for line in _term.wrap_kv(_term.key(key), value, indent=indent):
        print(line)


def handle_trace_show(
    args,
    *,
    maybe_auto_reconcile,
    read_run_record,
    read_trace_events_for_run,
    read_trace_events,
    print_trace_rows,
):
    maybe_auto_reconcile()
    run_id = str(args.run_id or "").strip()
    if not run_id:
        raise SystemExit("run_id is required")
    limit = max(1, min(1000, int(args.limit or 200)))
    record = read_run_record(run_id) or {}
    preferred_log = str(record.get("trace_log_path") or "").strip()
    events = read_trace_events_for_run(run_id, limit=limit, trace_log_path=preferred_log)
    if not events:
        events = read_trace_events(limit=limit, filters={"run_id": run_id})
    if args.json:
        for event in events:
            print(json.dumps(event, separators=(",", ":")))
        return 0
    print_trace_rows(events)
    return 0


def event_identity(event):
    trace_id = str(event.get("trace_id") or "").strip()
    if trace_id:
        return trace_id
    return json.dumps(
        {
            "timestamp": event.get("timestamp"),
            "run_id": event.get("run_id"),
            "event_type": event.get("event_type"),
            "outcome": event.get("outcome"),
            "payload": event.get("payload"),
        },
        sort_keys=True,
        separators=(",", ":"),
    )


def read_tail_events(*, read_trace_events, limit=50, run_id=""):
    bounded = max(1, min(20000, int(limit or 50)))
    filters = {}
    run_key = str(run_id or "").strip()
    if run_key:
        filters["run_id"] = run_key
    return read_trace_events(limit=bounded, filters=filters)


def emit_events(events, *, as_json=False, print_trace_rows):
    if as_json:
        for event in events:
            print(json.dumps(event, separators=(",", ":")))
        return
    print_trace_rows(events)


def handle_trace_tail(args, *, maybe_auto_reconcile, read_trace_events, print_trace_rows):
    maybe_auto_reconcile()
    limit = max(1, min(20000, int(args.limit or 50)))
    run_id = str(args.run_id or "").strip()
    events = read_tail_events(read_trace_events=read_trace_events, limit=limit, run_id=run_id)
    emit_events(events, as_json=bool(args.json), print_trace_rows=print_trace_rows)
    if not bool(args.follow):
        return 0

    seen = set()
    for event in events:
        seen.add(event_identity(event))
    try:
        while True:
            time.sleep(1.0)
            current = read_tail_events(read_trace_events=read_trace_events, limit=limit, run_id=run_id)
            fresh = []
            for event in current:
                key = event_identity(event)
                if key in seen:
                    continue
                fresh.append(event)
                seen.add(key)
            if fresh:
                emit_events(fresh, as_json=bool(args.json), print_trace_rows=print_trace_rows)
    except KeyboardInterrupt:
        return 0


def handle_trace_summary(
    args,
    *,
    maybe_auto_reconcile,
    read_run_record,
    read_trace_events_for_run,
    read_trace_events,
    build_run_summary,
    print_branding_stamp,
):
    maybe_auto_reconcile()
    run_id = str(args.run_id or "").strip()
    if not run_id:
        raise SystemExit("run_id is required")
    record = read_run_record(run_id)
    if not record:
        raise SystemExit(f"run_id not found: {run_id}")
    limit = max(1, min(20000, int(args.event_limit or 2000)))
    tail_lines = max(1, min(200, int(args.tail_lines or 20)))
    preferred_log = str(record.get("trace_log_path") or "").strip()
    events = read_trace_events_for_run(run_id, limit=limit, trace_log_path=preferred_log)
    if not events:
        events = read_trace_events(limit=limit, filters={"run_id": run_id})
    summary = build_run_summary(record, events, tail_lines=tail_lines)
    if args.json:
        print(json.dumps(summary, separators=(",", ":")))
        return 0
    print(_term.section("summary"))
    print(
        f"  {_term.key('run_id')}={summary['run_id']} {_term.key('status')}={_term.status_text(summary['status'])} "
        f"{_term.key('exit_code')}={summary['exit_code']} {_term.key('duration_ms')}={summary['duration_ms']} "
        f"{_term.key('events')}={summary['event_count']}"
    )
    _print_wrapped_kv("command", summary["command"])
    _print_wrapped_kv("cwd", summary["cwd"])
    print(
        f"  {_term.key('runtime_state')}={_term.status_text(summary['runtime_state'])} "
        f"{_term.key('last_activity_ms')}={summary['last_activity_ms']} "
        f"{_term.key('last_activity_age_ms')}={summary['last_activity_age_ms']} "
        f"{_term.key('last_heartbeat_ms')}={summary['last_heartbeat_ms']} "
        f"{_term.key('last_heartbeat_age_ms')}={summary['last_heartbeat_age_ms']}"
    )
    if summary["stderr_tail"]:
        print(_term.section("stderr_tail"))
        for line in summary["stderr_tail"]:
            for wrapped in _term.wrap_text(str(line), initial_indent="  ", subsequent_indent="  "):
                print(wrapped)
    if summary["stdout_tail"]:
        print(_term.section("stdout_tail"))
        for line in summary["stdout_tail"]:
            for wrapped in _term.wrap_text(str(line), initial_indent="  ", subsequent_indent="  "):
                print(wrapped)
    print(_term.section("suggestions"))
    for item in summary["suggestions"]:
        for wrapped in _term.wrap_text(str(item), initial_indent="  - ", subsequent_indent="    "):
            print(wrapped)
    print_branding_stamp(as_json=False)
    return 0


def handle_trace_diff(
    args,
    *,
    maybe_auto_reconcile,
    read_run_record,
    read_trace_events_for_run,
    build_run_diff,
    shorten,
    print_branding_stamp,
):
    maybe_auto_reconcile()
    run_id_a = str(args.run_id_a or "").strip()
    run_id_b = str(args.run_id_b or "").strip()
    if not run_id_a or not run_id_b:
        raise SystemExit("run_id_a and run_id_b are required")
    record_a = read_run_record(run_id_a)
    record_b = read_run_record(run_id_b)
    if not record_a:
        raise SystemExit(f"run_id not found: {run_id_a}")
    if not record_b:
        raise SystemExit(f"run_id not found: {run_id_b}")
    limit = max(1, min(20000, int(args.event_limit or 2000)))
    tail_lines = max(1, min(200, int(args.tail_lines or 20)))
    events_a = read_trace_events_for_run(run_id_a, limit=limit, trace_log_path=str(record_a.get("trace_log_path") or ""))
    events_b = read_trace_events_for_run(run_id_b, limit=limit, trace_log_path=str(record_b.get("trace_log_path") or ""))
    diff = build_run_diff(record_a, record_b, events_a, events_b, tail_lines=tail_lines)
    if args.json:
        print(json.dumps(diff, separators=(",", ":")))
        return 0
    print(_term.section("run_a"))
    print(
        f"  {_term.key('run_id')}={diff['run_a']['run_id']} {_term.key('status')}={_term.status_text(diff['run_a']['status'])} "
        f"{_term.key('duration_ms')}={diff['run_a']['duration_ms']}"
    )
    print(_term.section("run_b"))
    print(
        f"  {_term.key('run_id')}={diff['run_b']['run_id']} {_term.key('status')}={_term.status_text(diff['run_b']['status'])} "
        f"{_term.key('duration_ms')}={diff['run_b']['duration_ms']}"
    )
    if diff["config_deltas"]:
        print(_term.section("config_deltas"))
        for key, item in diff["config_deltas"].items():
            print(f"  {_term.key(key)}")
            _print_wrapped_kv("a", shorten(item.get("a"), limit=120), indent="    ")
            _print_wrapped_kv("b", shorten(item.get("b"), limit=120), indent="    ")
    else:
        print(_term.section("config_deltas"))
        print("  none")
    exec_meta = diff["execution"]
    print(_term.section("execution"))
    print(
        f"  {_term.key('events_a')}={exec_meta['events_a']} {_term.key('events_b')}={exec_meta['events_b']} "
        f"{_term.key('first_divergence_index')}={exec_meta['first_divergence_index']}"
    )
    out = diff["output"]
    print(_term.section("output_counts"))
    print(
        f"  {_term.key('stdout_a')}={out['stdout_count_a']} {_term.key('stdout_b')}={out['stdout_count_b']} "
        f"{_term.key('stderr_a')}={out['stderr_count_a']} {_term.key('stderr_b')}={out['stderr_count_b']}"
    )
    print(_term.section("hint"))
    _print_wrapped_kv("hint", diff["hint"])
    print_branding_stamp(as_json=False)
    return 0


def handle_trace_diagnose(
    args,
    *,
    maybe_auto_reconcile,
    read_run_record,
    read_trace_events_for_run,
    build_run_diagnosis,
    print_branding_stamp,
):
    maybe_auto_reconcile()
    run_id = str(args.run_id or "").strip()
    if not run_id:
        raise SystemExit("run_id is required")
    record = read_run_record(run_id)
    if not record:
        raise SystemExit(f"run_id not found: {run_id}")
    limit = max(1, min(20000, int(args.event_limit or 2000)))
    tail_lines = max(1, min(200, int(args.tail_lines or 20)))
    events = read_trace_events_for_run(run_id, limit=limit, trace_log_path=str(record.get("trace_log_path") or ""))
    diagnosis = build_run_diagnosis(record, events, tail_lines=tail_lines)
    if args.json:
        print(json.dumps(diagnosis, separators=(",", ":")))
        return 0
    print(_term.section("diagnosis"))
    print(
        f"  {_term.key('run_id')}={diagnosis['run_id']} {_term.key('status')}={_term.status_text(diagnosis['status'])} "
        f"{_term.key('category')}={diagnosis['category']} {_term.key('exit_code')}={diagnosis['exit_code']} "
        f"{_term.key('duration_ms')}={diagnosis['duration_ms']}"
    )
    if diagnosis["baseline_run_id"]:
        _print_wrapped_kv("baseline", diagnosis["baseline_run_id"])
    if diagnosis["stderr_tail"]:
        print(_term.section("stderr_tail"))
        for line in diagnosis["stderr_tail"]:
            for wrapped in _term.wrap_text(str(line), initial_indent="  ", subsequent_indent="  "):
                print(wrapped)
    if diagnosis["stdout_tail"]:
        print(_term.section("stdout_tail"))
        for line in diagnosis["stdout_tail"]:
            for wrapped in _term.wrap_text(str(line), initial_indent="  ", subsequent_indent="  "):
                print(wrapped)
    print(_term.section("recommendations"))
    for item in diagnosis["recommendations"]:
        for wrapped in _term.wrap_text(str(item), initial_indent="  - ", subsequent_indent="    "):
            print(wrapped)
    print_branding_stamp(as_json=False)
    return 0


def handle_trace_open(
    args,
    *,
    maybe_auto_reconcile,
    read_run_record,
    open_run_in_ui,
):
    maybe_auto_reconcile()
    run_id = str(args.run_id or "").strip()
    if not run_id:
        raise SystemExit("run_id is required")
    record = read_run_record(run_id)
    if not record:
        raise SystemExit(f"run_id not found: {run_id}")
    base_url = str(args.ui_url or os.environ.get("HIVE_TRACE_UI_URL") or "http://localhost:5173").strip()
    url = open_run_in_ui(base_url, run_id)
    _print_wrapped_kv("opened", url, indent="")
    return 0
