from __future__ import annotations

import json
import sys

import typer
from loguru import logger

from flow_xhs.cli.spec import (
    FLOW_XHS_SPEC,
    export_command_spec,
    export_group_view,
    export_root_help,
    export_topic_help,
    get_command_spec_or_alias,
    get_group_view,
    get_topic_spec,
    global_option,
    render_command_help,
    render_group_help,
    render_root_help,
    render_topic_help,
)
from flow_xhs.runtime.config import is_browser_headful_forced
from flow_xhs.runtime.database.manager import init_db
from flow_xhs import __version__
from flow_xhs.platform import REDNOTE_PLATFORM
from flow_xhs.platform.registration import register_flow_xhs_runtime
from flow_xhs.cli.commands import account, doctor, init, note, publish, search, user
from flow_xhs.cli.context import CliContext
from flow_xhs.runtime.logging import setup_cli_logger
from flow_xhs.runtime.paths import configure_legacy_data_paths, ensure_runtime_dirs

APP_HELP = render_root_help(FLOW_XHS_SPEC)

app = typer.Typer(help=APP_HELP, add_completion=False, no_args_is_help=True)

HELP_FLAGS = {"--help", "-h"}
HELP_VERBOSE_FLAGS = {"--verbose", "--verbose-help"}
GLOBAL_VALUE_OPTIONS = {"--format", "--out", "--trace-id", "--timeout"}
GLOBAL_FLAG_OPTIONS = {"--quiet", "--version"}

app.add_typer(init.app, name="init")
app.add_typer(doctor.app, name="doctor")
app.add_typer(account.app, name="account")
app.add_typer(note.app, name="note")
app.add_typer(user.app, name="user")
app.add_typer(search.app, name="search")
app.add_typer(publish.app, name="publish")


def _version_callback(value: bool) -> None:
    if not value:
        return

    typer.echo(__version__)
    raise typer.Exit(code=0)


@app.callback()
def main(
    ctx: typer.Context,
    format: str = global_option("format"),
    out: str | None = global_option("out"),
    quiet: bool = global_option("quiet", False),
    version: bool = global_option("version", False, callback=_version_callback, is_eager=True),
    trace_id: str | None = global_option("trace_id"),
    timeout: int = global_option("timeout"),
):
    if format not in {"json", "jsonl", "table"}:
        raise typer.BadParameter("--format 仅支持 json|jsonl|table")

    register_flow_xhs_runtime()
    ensure_runtime_dirs()
    configure_legacy_data_paths()
    init_db()
    setup_cli_logger(quiet=quiet)
    if is_browser_headful_forced():
        logger.warning("Browser headful mode forced by env (FLOW_XHS_HEADFUL/BROWSER_HEADFUL).")

    ctx.obj = CliContext(
        platform_name=REDNOTE_PLATFORM,
        output_format=format,
        out_file=out,
        quiet=quiet,
        trace_id=trace_id,
        timeout=timeout,
    )


def _normalize_global_options_position() -> None:
    """Allow global options to appear after subcommands.

    Click/Typer requires group options to appear before subcommands by default.
    This shim rewrites argv so both forms work:
    - flow-xhs --format json doctor run
    - flow-xhs doctor run --format json
    """
    if len(sys.argv) <= 1:
        return

    command_names = {"init", "doctor", "account", "note", "user", "search", "publish"}
    value_options = GLOBAL_VALUE_OPTIONS
    flag_options = GLOBAL_FLAG_OPTIONS
    all_options = value_options | flag_options

    raw_args = sys.argv[1:]
    moved: list[str] = []
    kept: list[str] = []

    def _die_missing_value(option: str) -> None:
        option_examples = {
            "--format": "flow-xhs doctor run --format json",
            "--out": "flow-xhs doctor run --out result.json",
            "--trace-id": "flow-xhs doctor run --trace-id req-001",
            "--timeout": "flow-xhs doctor run --timeout 120",
        }
        example = option_examples.get(option, f"flow-xhs doctor run {option} <value>")
        typer.echo(
            f"Error: `{option}` 需要一个值。例如：`{example}`",
            err=True,
        )
        raise SystemExit(2)

    i = 0
    while i < len(raw_args):
        token = raw_args[i]
        if token == "--":
            kept.extend(raw_args[i:])
            break

        if token in flag_options:
            moved.append(token)
            i += 1
            continue

        if token in value_options:
            if i + 1 >= len(raw_args):
                _die_missing_value(token)
            next_token = raw_args[i + 1]
            if next_token == "--":
                _die_missing_value(token)
            if token == "--out" and next_token in command_names:
                _die_missing_value(token)
            moved.append(token)
            moved.append(next_token)
            i += 2
            continue

        matched_inline = False
        for opt in all_options:
            prefix = f"{opt}="
            if token.startswith(prefix):
                moved.append(token)
                matched_inline = True
                break
        if matched_inline:
            i += 1
            continue

        kept.append(token)
        i += 1

    if moved:
        sys.argv = [sys.argv[0], *moved, *kept]


def _skip_option_value(args: list[str], index: int) -> int:
    token = args[index]
    if token in GLOBAL_VALUE_OPTIONS and "=" not in token and index + 1 < len(args):
        return index + 2
    return index + 1


def _extract_help_path(args: list[str]) -> tuple[tuple[str, ...], bool]:
    path: list[str] = []
    started_path = False
    saw_help = False
    i = 0
    while i < len(args):
        token = args[i]
        if token in HELP_FLAGS:
            saw_help = True
            break
        if token.startswith("-"):
            if started_path:
                break
            i = _skip_option_value(args, i)
            continue
        started_path = True
        path.append(token)
        i += 1
    return tuple(path), saw_help


def _extract_command_path(args: list[str]) -> tuple[tuple[str, ...], list[str]]:
    path: list[str] = []
    i = 0
    while i < len(args):
        token = args[i]
        if token.startswith("-"):
            i = _skip_option_value(args, i)
            continue
        break
    while i < len(args):
        token = args[i]
        if token.startswith("-"):
            break
        path.append(token)
        i += 1
    return tuple(path), args[i:]


def _extract_explicit_help_path(args: list[str]) -> tuple[str, ...] | None:
    i = 0
    while i < len(args):
        token = args[i]
        if token.startswith("-"):
            i = _skip_option_value(args, i)
            continue
        if token != "help":
            return None
        path: list[str] = []
        i += 1
        while i < len(args):
            token = args[i]
            if token.startswith("-"):
                break
            path.append(token)
            i += 1
        return tuple(path)
    return None


def _extract_global_option_value(args: list[str], option_name: str) -> str | None:
    i = 0
    while i < len(args):
        token = args[i]
        if token == option_name:
            if i + 1 >= len(args):
                return None
            return args[i + 1]
        prefix = f"{option_name}="
        if token.startswith(prefix):
            return token[len(prefix):]
        i = _skip_option_value(args, i)
    return None


def _has_global_flag(args: list[str], option_name: str) -> bool:
    return option_name in args


def _help_verbose_enabled(args: list[str]) -> bool:
    return any(flag in args for flag in HELP_VERBOSE_FLAGS)


def _render_spec_output(*, text: str, payload: dict[str, object] | None, args: list[str]) -> None:
    output_format = _extract_global_option_value(args, "--format") or "table"
    if output_format in {"json", "jsonl"}:
        typer.echo(json.dumps(payload or {"kind": "text", "text": text}, ensure_ascii=False))
        return
    typer.echo(text)


def _try_render_spec_help() -> bool:
    args = sys.argv[1:]
    verbose = _help_verbose_enabled(args)
    if not args:
        _render_spec_output(
            text=render_root_help(FLOW_XHS_SPEC, verbose=verbose),
            payload=export_root_help(FLOW_XHS_SPEC),
            args=args,
        )
        return True

    explicit_help_path = _extract_explicit_help_path(args)
    if explicit_help_path is not None:
        if not explicit_help_path:
            _render_spec_output(
                text=render_root_help(FLOW_XHS_SPEC, verbose=verbose),
                payload=export_root_help(FLOW_XHS_SPEC),
                args=args,
            )
            return True
        group = get_group_view(FLOW_XHS_SPEC, explicit_help_path)
        if group is not None:
            _render_spec_output(
                text=render_group_help(group, verbose=verbose),
                payload=export_group_view(group),
                args=args,
            )
            return True
        if len(explicit_help_path) == 1:
            topic = get_topic_spec(FLOW_XHS_SPEC, explicit_help_path[0])
            if topic is not None:
                _render_spec_output(
                    text=render_topic_help(topic),
                    payload=export_topic_help(topic),
                    args=args,
                )
                return True
        command = get_command_spec_or_alias(FLOW_XHS_SPEC, explicit_help_path)
        if command is None:
            return False
        _render_spec_output(
            text=render_command_help(FLOW_XHS_SPEC, command, invoked_path=explicit_help_path, verbose=verbose),
            payload=export_command_spec(command),
            args=args,
        )
        return True

    path, saw_help = _extract_help_path(args)
    if saw_help:
        if not path:
            _render_spec_output(
                text=render_root_help(FLOW_XHS_SPEC, verbose=verbose),
                payload=export_root_help(FLOW_XHS_SPEC),
                args=args,
            )
            return True
        group = get_group_view(FLOW_XHS_SPEC, path)
        if group is not None:
            _render_spec_output(
                text=render_group_help(group, verbose=verbose),
                payload=export_group_view(group),
                args=args,
            )
            return True
        command = get_command_spec_or_alias(FLOW_XHS_SPEC, path)
        if command is None:
            return False
        _render_spec_output(
            text=render_command_help(FLOW_XHS_SPEC, command, invoked_path=path, verbose=verbose),
            payload=export_command_spec(command),
            args=args,
        )
        return True

    if _has_global_flag(args, "--version"):
        return False

    path, trailing = _extract_command_path(args)
    if not path and not trailing:
        _render_spec_output(
            text=render_root_help(FLOW_XHS_SPEC, verbose=verbose),
            payload=export_root_help(FLOW_XHS_SPEC),
            args=args,
        )
        return True
    if path and not trailing:
        group = get_group_view(FLOW_XHS_SPEC, path)
        if group is not None:
            _render_spec_output(
                text=render_group_help(group, verbose=verbose),
                payload=export_group_view(group),
                args=args,
            )
            return True

    return False


def run() -> None:
    _normalize_global_options_position()
    if _try_render_spec_help():
        return
    app()


if __name__ == "__main__":
    run()
