from __future__ import annotations

import sys

import typer
from loguru import logger

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 = """
Flow XHS CLI（非驻留，一次命令一次进程）。

推荐给 AI/Agent 的调用约定：
- 输出固定使用 `--format json`
- 每次调用传 `--trace-id`
- 需要批量参数时使用 `--input <json-file|->`

输入优先级：
- 显式 CLI 参数 > `--input` JSON 同名字段 > 默认值

输出契约与错误码：
- 成功/失败 JSON 结构见 `schemas/*.json`
- 退出码：0 成功，2 参数错，3 认证错，4 风控，5 内部错，6 超时，7 依赖缺失/未实现

快速开始：
- `flow-xhs init runtime`
- `flow-xhs doctor run --format json`
- `flow-xhs search note --keyword 旅行 --size 10 --sort-by latest --note-type image_text --format json`
- `flow-xhs note --note-id <id> --format json`
""".strip()

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

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 = typer.Option("table", "--format", help="输出格式：json(推荐给 Agent) | jsonl(流式) | table(人工查看)"),
    out: str | None = typer.Option(None, "--out", help="将最终输出写入文件；stderr 日志不受影响"),
    quiet: bool = typer.Option(False, "--quiet", help="静默日志，仅输出结果"),
    version: bool = typer.Option(False, "--version", callback=_version_callback, is_eager=True, help="显示 flow-xhs 版本并退出"),
    trace_id: str | None = typer.Option(None, "--trace-id", help="请求链路 ID，方便排查和关联日志/结果；建议外部系统全程透传"),
    timeout: int = typer.Option(120, "--timeout", help="命令超时（秒）"),
):
    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 = {"--format", "--out", "--trace-id", "--timeout"}
    flag_options = {"--quiet", "--version"}
    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 run() -> None:
    _normalize_global_options_position()
    app()


if __name__ == "__main__":
    run()
