from __future__ import annotations

import argparse
from typing import Any

from ...errors import QingflowApiError
from ..context import CliContext
from .common import load_list_arg, load_object_arg, raise_config_error, require_list_arg, require_object_arg


def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) -> None:
    parser = subparsers.add_parser("record", help="记录与表结构")
    record_subparsers = parser.add_subparsers(
        dest="record_command",
        required=True,
        metavar="{schema,list,access,get,logs,insert,update,delete,member-candidates,department-candidates,code-block-run}",
    )

    schema = record_subparsers.add_parser("schema", help="读取记录相关表结构")
    schema.add_argument("--mode", dest="legacy_mode", help=argparse.SUPPRESS)
    schema_subparsers = schema.add_subparsers(
        dest="record_schema_command",
        metavar="{browse,insert,update,import,code-block}",
    )
    schema.set_defaults(handler=_handle_schema_root, format_hint="")

    schema_applicant = schema_subparsers.add_parser("applicant", help=argparse.SUPPRESS)
    schema_applicant.add_argument("--app-key", required=True)
    schema_applicant.set_defaults(handler=_handle_schema_applicant, format_hint="record_schema")
    schema_subparsers._choices_actions = [  # type: ignore[attr-defined]
        action
        for action in schema_subparsers._choices_actions  # type: ignore[attr-defined]
        if getattr(action, "dest", None) != "applicant"
    ]

    schema_browse = schema_subparsers.add_parser("browse", help="读取浏览视图表结构")
    schema_browse.add_argument("--app-key", required=True)
    schema_browse.add_argument("--view-id", required=True)
    schema_browse.set_defaults(handler=_handle_schema_browse, format_hint="record_schema")

    schema_insert = schema_subparsers.add_parser("insert", help="读取新增记录表结构")
    schema_insert.add_argument("--app-key", required=True)
    schema_insert.set_defaults(handler=_handle_schema_insert, format_hint="record_schema")

    schema_update = schema_subparsers.add_parser("update", help="读取更新记录表结构")
    schema_update.add_argument("--app-key", required=True)
    schema_update.add_argument("--record-id", required=True)
    schema_update.add_argument("--view-id")
    schema_update.set_defaults(handler=_handle_schema_update, format_hint="record_schema")

    schema_import = schema_subparsers.add_parser("import", help="读取导入表结构")
    schema_import.add_argument("--app-key", required=True)
    schema_import.set_defaults(handler=_handle_schema_import, format_hint="record_schema")

    schema_code_block = schema_subparsers.add_parser("code-block", help="读取代码块执行表结构")
    schema_code_block.add_argument("--app-key", required=True)
    schema_code_block.set_defaults(handler=_handle_schema_code_block, format_hint="record_schema")

    member_candidates = record_subparsers.add_parser("member-candidates", help="读取成员字段候选项")
    member_candidates.add_argument("--app-key", required=True)
    member_candidates.add_argument("--field-id", type=int, required=True)
    member_candidates.add_argument("--keyword", default="")
    member_candidates.add_argument("--page-num", type=int, default=1)
    member_candidates.add_argument("--page-size", type=int, default=20)
    member_candidates.add_argument("--record-id")
    member_candidates.add_argument("--workflow-node-id", type=int)
    member_candidates.add_argument("--fields-file")
    member_candidates.set_defaults(handler=_handle_member_candidates, format_hint="record_candidates")

    department_candidates = record_subparsers.add_parser("department-candidates", help="读取部门字段候选项")
    department_candidates.add_argument("--app-key", required=True)
    department_candidates.add_argument("--field-id", type=int, required=True)
    department_candidates.add_argument("--keyword", default="")
    department_candidates.add_argument("--page-num", type=int, default=1)
    department_candidates.add_argument("--page-size", type=int, default=20)
    department_candidates.add_argument("--record-id")
    department_candidates.add_argument("--workflow-node-id", type=int)
    department_candidates.add_argument("--fields-file")
    department_candidates.set_defaults(handler=_handle_department_candidates, format_hint="record_candidates")

    list_parser = record_subparsers.add_parser("list", help="列出记录")
    list_parser.add_argument("--app-key", required=True)
    list_parser.add_argument(
        "--column",
        dest="columns",
        action="append",
        type=int,
        default=[],
        metavar="FIELD_ID",
        help="只返回这些 field_id；可重复传",
    )
    list_parser.add_argument(
        "--columns-file",
        help="JSON/YAML list；元素为 field_id 整数/整数字符串或 {'field_id': ...}",
    )
    list_parser.add_argument("--query")
    list_parser.add_argument(
        "--query-field",
        dest="query_fields",
        action="append",
        type=int,
        default=[],
        metavar="FIELD_ID",
        help="全文搜索范围 field_id；可重复传",
    )
    list_parser.add_argument(
        "--query-fields-file",
        help="JSON/YAML list；元素为 field_id 整数/整数字符串或 {'field_id': ...}",
    )
    list_parser.add_argument("--where-file")
    list_parser.add_argument("--order-by-file")
    list_parser.add_argument("--page", type=int, default=1)
    list_parser.add_argument("--page-size", type=int, default=10)
    list_parser.add_argument("--view-id")
    list_parser.add_argument("--list-type", dest="legacy_list_type", type=int, help=argparse.SUPPRESS)
    list_parser.add_argument("--view-key", dest="legacy_view_key", help=argparse.SUPPRESS)
    list_parser.add_argument("--view-name", dest="legacy_view_name", help=argparse.SUPPRESS)
    list_parser.set_defaults(handler=_handle_list, format_hint="record_list")

    access_parser = record_subparsers.add_parser("access", help="访问记录并写入本地 CSV 分片")
    access_parser.add_argument("--app-key", required=True)
    access_parser.add_argument(
        "--column",
        dest="columns",
        action="append",
        type=int,
        default=[],
        metavar="FIELD_ID",
        help="导出这些 field_id 到 CSV；可重复传",
    )
    access_parser.add_argument(
        "--columns-file",
        help="JSON/YAML list；元素为 field_id 整数/整数字符串或 {'field_id': ...}",
    )
    access_parser.add_argument("--where-file")
    access_parser.add_argument("--order-by-file")
    access_parser.add_argument("--view-id", required=True)
    access_parser.set_defaults(handler=_handle_access, format_hint="record_access")

    get = record_subparsers.add_parser("get", help="读取单条记录")
    get.add_argument("--app-key", required=True)
    get.add_argument("--record-id", required=True)
    get.add_argument(
        "--column",
        dest="columns",
        action="append",
        type=int,
        default=[],
        metavar="FIELD_ID",
        help="聚焦这些 field_id；可重复传",
    )
    get.add_argument(
        "--columns-file",
        help="JSON/YAML list；元素为 field_id 整数/整数字符串或 {'field_id': ...}",
    )
    get.add_argument("--view-id")
    get.set_defaults(handler=_handle_get, format_hint="record_get")

    logs = record_subparsers.add_parser("logs", help="读取单条记录全量日志并写入本地 JSONL")
    logs.add_argument("--app-key", required=True)
    logs.add_argument("--record-id", required=True)
    logs.add_argument("--view-id", required=True)
    logs.set_defaults(handler=_handle_logs, format_hint="record_logs")

    insert = record_subparsers.add_parser("insert", help="新增记录")
    insert.add_argument("--app-key", required=True)
    insert.add_argument("--fields-file", help=argparse.SUPPRESS)
    insert.add_argument("--items-file")
    insert.add_argument("--verify-write", action=argparse.BooleanOptionalAction, default=True)
    insert.set_defaults(handler=_handle_insert, format_hint="record_write")

    update = record_subparsers.add_parser("update", help="更新记录")
    update.add_argument("--app-key", required=True)
    update.add_argument("--record-id")
    update.add_argument("--fields-file")
    update.add_argument("--items-file")
    update.add_argument("--view-id")
    update.add_argument("--dry-run", action=argparse.BooleanOptionalAction, default=False)
    update.add_argument("--verify-write", action=argparse.BooleanOptionalAction, default=True)
    update.set_defaults(handler=_handle_update, format_hint="record_write")

    delete = record_subparsers.add_parser("delete", help="删除记录")
    delete.add_argument("--app-key", required=True)
    delete.add_argument("--record-id")
    delete.add_argument("--record-ids-file")
    delete.add_argument("--view-id")
    delete.add_argument("--list-type", dest="legacy_list_type", type=int, help=argparse.SUPPRESS)
    delete.set_defaults(handler=_handle_delete, format_hint="record_delete")

    analyze = record_subparsers.add_parser("analyze", help=argparse.SUPPRESS)
    record_subparsers._choices_actions = [  # type: ignore[attr-defined]
        action
        for action in record_subparsers._choices_actions  # type: ignore[attr-defined]
        if getattr(action, "dest", None) != "analyze"
    ]
    analyze.add_argument("--app-key", required=True)
    analyze.add_argument("--dimensions-file")
    analyze.add_argument("--metrics-file")
    analyze.add_argument("--filters-file")
    analyze.add_argument("--sort-file")
    analyze.add_argument("--limit", type=int, default=20)
    analyze.add_argument("--strict-full", action=argparse.BooleanOptionalAction, default=False)
    analyze.add_argument("--view-id")
    analyze.add_argument("--list-type", dest="legacy_list_type", type=int, help=argparse.SUPPRESS)
    analyze.add_argument("--view-key", dest="legacy_view_key", help=argparse.SUPPRESS)
    analyze.add_argument("--view-name", dest="legacy_view_name", help=argparse.SUPPRESS)
    analyze.set_defaults(handler=_handle_analyze, format_hint="")

    code_block = record_subparsers.add_parser("code-block-run", help="执行代码块字段")
    code_block.add_argument("--app-key", required=True)
    code_block.add_argument("--record-id", required=True)
    code_block.add_argument("--code-block-field", required=True)
    code_block.add_argument("--view-id")
    code_block.add_argument("--role", type=int, default=1)
    code_block.add_argument("--workflow-node-id", type=int)
    code_block.add_argument("--answers-file")
    code_block.add_argument("--fields-file")
    code_block.add_argument("--manual", action=argparse.BooleanOptionalAction, default=True)
    code_block.add_argument("--apply-writeback", action=argparse.BooleanOptionalAction, default=True)
    code_block.add_argument("--verify-writeback", action=argparse.BooleanOptionalAction, default=True)
    code_block.add_argument("--force-refresh-form", action="store_true")
    code_block.set_defaults(handler=_handle_code_block_run, format_hint="code_block_run")


def _columns(args: argparse.Namespace) -> list[Any]:
    columns: list[Any] = list(args.columns or [])
    if args.columns_file:
        columns.extend(require_list_arg(args.columns_file, option_name="--columns-file"))
    return columns


def _query_fields(args: argparse.Namespace) -> list[Any]:
    query_fields: list[Any] = list(getattr(args, "query_fields", None) or [])
    if getattr(args, "query_fields_file", None):
        query_fields.extend(require_list_arg(args.query_fields_file, option_name="--query-fields-file"))
    return query_fields


def _handle_schema_root(args: argparse.Namespace, _context: CliContext) -> dict:
    mode = (args.legacy_mode or "").strip()
    if mode:
        replacement = {
            "applicant": "record schema insert --app-key APP_KEY",
            "browse": "record schema browse --app-key APP_KEY --view-id VIEW_ID",
            "insert": "record schema insert --app-key APP_KEY",
            "update": "record schema update --app-key APP_KEY --record-id RECORD_ID [--view-id VIEW_ID]",
            "import": "record schema import --app-key APP_KEY",
            "code-block": "record schema code-block --app-key APP_KEY",
        }.get(mode, "record schema <browse|insert|update|import|code-block> ...")
        raise_config_error(
            "record schema --mode is no longer accepted.",
            fix_hint=f"Use `{replacement}` instead.",
        )
    raise_config_error(
        "record schema requires an explicit subcommand.",
        fix_hint="Use one of: `record schema browse`, `record schema insert`, `record schema update`, `record schema import`, or `record schema code-block`.",
    )


def _handle_schema_applicant(args: argparse.Namespace, context: CliContext) -> dict:
    return context.record.record_schema_get(
        profile=args.profile,
        app_key=args.app_key,
        schema_mode="applicant",
    )


def _handle_schema_browse(args: argparse.Namespace, context: CliContext) -> dict:
    return context.record.record_browse_schema_get_public(
        profile=args.profile,
        app_key=args.app_key,
        view_id=args.view_id,
    )


def _handle_schema_insert(args: argparse.Namespace, context: CliContext) -> dict:
    return context.record.record_insert_schema_get_public(profile=args.profile, app_key=args.app_key)


def _handle_schema_update(args: argparse.Namespace, context: CliContext) -> dict:
    return context.record.record_update_schema_get_public(
        profile=args.profile,
        app_key=args.app_key,
        record_id=args.record_id,
        view_id=args.view_id,
    )


def _handle_schema_import(args: argparse.Namespace, context: CliContext) -> dict:
    return context.imports.record_import_schema_get(profile=args.profile, app_key=args.app_key)


def _handle_schema_code_block(args: argparse.Namespace, context: CliContext) -> dict:
    return context.code_block.record_code_block_schema_get_public(profile=args.profile, app_key=args.app_key)


def _candidate_context_fields(args: argparse.Namespace) -> dict[str, Any]:
    return load_object_arg(args.fields_file, option_name="--fields-file") or {}


def _handle_member_candidates(args: argparse.Namespace, context: CliContext) -> dict:
    return context.record.record_member_candidates(
        profile=args.profile,
        app_key=args.app_key,
        field_id=args.field_id,
        record_id=args.record_id,
        workflow_node_id=args.workflow_node_id,
        fields=_candidate_context_fields(args),
        keyword=args.keyword,
        page_num=args.page_num,
        page_size=args.page_size,
    )


def _handle_department_candidates(args: argparse.Namespace, context: CliContext) -> dict:
    return context.record.record_department_candidates(
        profile=args.profile,
        app_key=args.app_key,
        field_id=args.field_id,
        record_id=args.record_id,
        workflow_node_id=args.workflow_node_id,
        fields=_candidate_context_fields(args),
        keyword=args.keyword,
        page_num=args.page_num,
        page_size=args.page_size,
    )


def _validate_public_view_selector(
    *,
    view_id: str | None,
    legacy_list_type: int | None,
    legacy_view_key: str | None,
    legacy_view_name: str | None,
    tool_name: str,
) -> None:
    if legacy_list_type is not None:
        raise_config_error(
            f"{tool_name} no longer accepts list_type.",
            fix_hint="Call `app_get` first and use `accessible_views[].view_id`.",
        )
    if legacy_view_key or legacy_view_name:
        raise_config_error(
            f"{tool_name} no longer accepts view_key or view_name.",
            fix_hint="Call `app_get` first and pass the exact `view_id` from `accessible_views`.",
        )
    if not (view_id or "").strip():
        raise_config_error(
            f"{tool_name} requires view_id.",
            fix_hint="Call `app_get` first and choose one `accessible_views[].view_id`, then retry with `--view-id`.",
        )


def _handle_list(args: argparse.Namespace, context: CliContext) -> dict:
    _validate_public_view_selector(
        view_id=args.view_id,
        legacy_list_type=args.legacy_list_type,
        legacy_view_key=args.legacy_view_key,
        legacy_view_name=args.legacy_view_name,
        tool_name="record_list",
    )
    return context.record.record_list(
        profile=args.profile,
        app_key=args.app_key,
        columns=_columns(args),
        query=args.query,
        query_fields=_query_fields(args),
        where=load_list_arg(args.where_file, option_name="--where-file"),
        order_by=load_list_arg(args.order_by_file, option_name="--order-by-file"),
        page=args.page,
        page_size=args.page_size,
        view_id=args.view_id,
    )


def _handle_access(args: argparse.Namespace, context: CliContext) -> dict:
    return context.record.record_access(
        profile=args.profile,
        app_key=args.app_key,
        columns=_columns(args),
        where=load_list_arg(args.where_file, option_name="--where-file"),
        order_by=load_list_arg(args.order_by_file, option_name="--order-by-file"),
        view_id=args.view_id,
    )


def _handle_get(args: argparse.Namespace, context: CliContext) -> dict:
    return context.record.record_get_public(
        profile=args.profile,
        app_key=args.app_key,
        record_id=args.record_id,
        columns=_columns(args),
        view_id=args.view_id,
    )


def _handle_logs(args: argparse.Namespace, context: CliContext) -> dict:
    return context.record.record_logs_get(
        profile=args.profile,
        app_key=args.app_key,
        record_id=args.record_id,
        view_id=args.view_id,
    )


def _handle_insert(args: argparse.Namespace, context: CliContext) -> dict:
    if args.items_file:
        if args.fields_file:
            raise_config_error(
                "record insert batch mode does not accept --fields-file.",
                fix_hint="Use `record insert --app-key APP_KEY --items-file ITEMS.json` for batch inserts.",
            )
        return context.record.record_insert_public(
            profile=args.profile,
            app_key=args.app_key,
            items=require_list_arg(args.items_file, option_name="--items-file"),
            verify_write=bool(args.verify_write),
        )
    if not args.fields_file:
        raise_config_error(
            "record insert requires --items-file.",
            fix_hint="Use `record insert --app-key APP_KEY --items-file ITEMS.json`; a single insert is one item in the JSON array.",
        )
    return context.record.record_insert_public(
        profile=args.profile,
        app_key=args.app_key,
        fields=require_object_arg(args.fields_file, option_name="--fields-file"),
        verify_write=bool(args.verify_write),
    )


def _handle_update(args: argparse.Namespace, context: CliContext) -> dict:
    if args.items_file:
        if args.record_id is not None or args.fields_file:
            raise_config_error(
                "record update batch mode does not accept --record-id or --fields-file.",
                fix_hint="Use `record update --app-key APP_KEY --items-file ITEMS.json [--dry-run]` for batch updates.",
            )
        return context.record.record_update_public(
            profile=args.profile,
            app_key=args.app_key,
            record_id=None,
            fields=None,
            items=require_list_arg(args.items_file, option_name="--items-file"),
            view_id=args.view_id,
            dry_run=bool(args.dry_run),
            verify_write=bool(args.verify_write),
        )
    if args.dry_run:
        raise_config_error(
            "record update --dry-run currently requires --items-file.",
            fix_hint="Use `record update --app-key APP_KEY --items-file ITEMS.json --dry-run` for batch preflight.",
        )
    if args.record_id is None or not args.fields_file:
        raise_config_error(
            "record update single mode requires --record-id and --fields-file.",
            fix_hint="Use `record update --app-key APP_KEY --record-id RECORD_ID --fields-file FIELDS.json`.",
        )
    return context.record.record_update_public(
        profile=args.profile,
        app_key=args.app_key,
        record_id=args.record_id,
        fields=require_object_arg(args.fields_file, option_name="--fields-file"),
        view_id=args.view_id,
        verify_write=bool(args.verify_write),
    )


def _handle_delete(args: argparse.Namespace, context: CliContext) -> dict:
    if args.legacy_list_type is not None:
        raise_config_error(
            "record delete no longer accepts list_type.",
            fix_hint="Call `app_get` first and pass an accessible system `view_id`, for example `--view-id system:all`.",
        )
    if not (args.view_id or "").strip():
        raise_config_error(
            "record delete requires --view-id.",
            fix_hint="Use a system view_id from app get accessible_views, for example `--view-id system:all`; custom views are not supported for deletion.",
        )
    record_ids = load_list_arg(args.record_ids_file, option_name="--record-ids-file")
    return context.record.record_delete_public(
        profile=args.profile,
        app_key=args.app_key,
        record_id=args.record_id,
        record_ids=record_ids,
        view_id=args.view_id,
    )


def _handle_analyze(args: argparse.Namespace, context: CliContext) -> dict:
    _validate_public_view_selector(
        view_id=args.view_id,
        legacy_list_type=args.legacy_list_type,
        legacy_view_key=args.legacy_view_key,
        legacy_view_name=args.legacy_view_name,
        tool_name="record_analyze",
    )
    return context.record.record_analyze(
        profile=args.profile,
        app_key=args.app_key,
        dimensions=load_list_arg(args.dimensions_file, option_name="--dimensions-file"),
        metrics=load_list_arg(args.metrics_file, option_name="--metrics-file"),
        filters=load_list_arg(args.filters_file, option_name="--filters-file"),
        sort=load_list_arg(args.sort_file, option_name="--sort-file"),
        limit=args.limit,
        strict_full=bool(args.strict_full),
        view_id=args.view_id,
    )


def _handle_code_block_run(args: argparse.Namespace, context: CliContext) -> dict:
    if args.workflow_node_id is not None and args.role != 3:
        raise_config_error(
            "workflow_node_id is only accepted when role=3.",
            fix_hint="Remove `--workflow-node-id`, or set `--role 3` when running in workflow context.",
        )
    return context.code_block.record_code_block_run(
        profile=args.profile,
        app_key=args.app_key,
        record_id=args.record_id,
        code_block_field=args.code_block_field,
        view_id=args.view_id,
        role=args.role,
        workflow_node_id=args.workflow_node_id,
        answers=load_list_arg(args.answers_file, option_name="--answers-file"),
        fields=load_object_arg(args.fields_file, option_name="--fields-file") or {},
        manual=bool(args.manual),
        apply_writeback=bool(args.apply_writeback),
        verify_writeback=bool(args.verify_writeback),
        force_refresh_form=bool(args.force_refresh_form),
    )
