from __future__ import annotations

import argparse
import copy
from dataclasses import dataclass
from typing import Any, Callable, Dict, List, Optional, Sequence

from autotouch_cli.exceptions import AutotouchInputError


@dataclass(frozen=True)
class TableCommandRuntime:
    resolve_token: Callable[[Optional[str], bool], Optional[str]]
    request_api: Callable[..., Any]
    print_json: Callable[[Any, bool], None]
    load_json_input: Callable[..., Any]


def cmd_tables_list(args: argparse.Namespace, *, runtime: TableCommandRuntime) -> None:
    token = runtime.resolve_token(args.token, required=True)
    params: Dict[str, Any] = {}
    if args.view_mode:
        params["view_mode"] = args.view_mode
    data = runtime.request_api(
        "GET",
        "/api/tables",
        base_url=args.base_url,
        token=token,
        use_x_api_key=args.use_x_api_key,
        params=params or None,
        timeout=args.timeout,
        verbose=args.verbose,
    )
    runtime.print_json(data, args.compact)


def cmd_tables_create(args: argparse.Namespace, *, runtime: TableCommandRuntime) -> None:
    token = runtime.resolve_token(args.token, required=True)
    data_payload = runtime.load_json_input(
        inline_json=args.data_json,
        file_path=args.data_file,
        context="data",
        default=None,
    )
    if data_payload is not None:
        if not isinstance(data_payload, dict):
            raise AutotouchInputError("table create payload must be a JSON object")
        payload = data_payload
    else:
        if not args.name:
            raise AutotouchInputError("--name is required when --data-* is not provided")
        metadata = runtime.load_json_input(
            inline_json=args.metadata_json,
            file_path=args.metadata_file,
            context="metadata",
            default={},
        )
        if metadata is None:
            metadata = {}
        if not isinstance(metadata, dict):
            raise AutotouchInputError("metadata must be a JSON object")
        payload = {"name": args.name, "metadata": metadata}

    data = runtime.request_api(
        "POST",
        "/api/tables",
        base_url=args.base_url,
        token=token,
        use_x_api_key=args.use_x_api_key,
        payload=payload,
        timeout=args.timeout,
        verbose=args.verbose,
    )
    runtime.print_json(data, args.compact)


def cmd_tables_update(args: argparse.Namespace, *, runtime: TableCommandRuntime) -> None:
    token = runtime.resolve_token(args.token, required=True)
    payload = runtime.load_json_input(
        inline_json=getattr(args, "data_json", None),
        file_path=getattr(args, "data_file", None),
        context="data",
        default=None,
    )
    if payload is None:
        metadata = runtime.load_json_input(
            inline_json=getattr(args, "metadata_json", None),
            file_path=getattr(args, "metadata_file", None),
            context="metadata",
            default=None,
        )
        if metadata is None:
            raise AutotouchInputError("provide --metadata-json, --metadata-file, or --data-json/--data-file")
        if not isinstance(metadata, dict):
            raise AutotouchInputError("metadata must be a JSON object")
        payload = {"metadata": metadata}
    if not isinstance(payload, dict):
        raise AutotouchInputError("update payload must be a JSON object")
    data = runtime.request_api(
        "PATCH",
        f"/api/tables/{args.table_id}",
        base_url=args.base_url,
        token=token,
        use_x_api_key=args.use_x_api_key,
        payload=payload,
        timeout=args.timeout,
        verbose=args.verbose,
    )
    runtime.print_json(data, args.compact)


def cmd_tables_identity_get(args: argparse.Namespace, *, runtime: TableCommandRuntime) -> None:
    token = runtime.resolve_token(args.token, required=True)
    data = runtime.request_api(
        "GET",
        f"/api/tables/{args.table_id}/info",
        base_url=args.base_url,
        token=token,
        use_x_api_key=args.use_x_api_key,
        timeout=args.timeout,
        verbose=args.verbose,
    )
    if isinstance(data, dict):
        ingest_identity = (data.get("metadata") or {}).get("ingest_identity")
        runtime.print_json(ingest_identity if ingest_identity is not None else {}, args.compact)
    else:
        runtime.print_json(data, args.compact)


def cmd_tables_identity_set(args: argparse.Namespace, *, runtime: TableCommandRuntime) -> None:
    token = runtime.resolve_token(args.token, required=True)
    info = runtime.request_api(
        "GET",
        f"/api/tables/{args.table_id}/info",
        base_url=args.base_url,
        token=token,
        use_x_api_key=args.use_x_api_key,
        timeout=args.timeout,
        verbose=args.verbose,
    )
    existing_rules: List[Dict[str, Any]] = []
    if isinstance(info, dict):
        existing_rules = list((info.get("metadata") or {}).get("ingest_identity", {}).get("rules") or [])

    rule_id = str(args.rule_id or args.column_key).strip()
    new_rule: Dict[str, Any] = {
        "id": rule_id,
        "column_key": args.column_key,
        "match_strategy": args.match_strategy,
        "enabled": not bool(getattr(args, "disabled", False)),
    }
    if getattr(args, "source", None) and args.source != "default":
        new_rule["source"] = args.source
    if getattr(args, "mode", None):
        new_rule["duplicate_mode"] = args.mode
    if getattr(args, "merge_mode", None):
        new_rule["merge_mode"] = args.merge_mode

    updated_rules = [r for r in existing_rules if r.get("id") != rule_id]
    updated_rules.append(new_rule)

    ingest_identity: Dict[str, Any] = {"rules": updated_rules}
    payload: Dict[str, Any] = {"metadata": {"ingest_identity": ingest_identity}}
    data = runtime.request_api(
        "PATCH",
        f"/api/tables/{args.table_id}",
        base_url=args.base_url,
        token=token,
        use_x_api_key=args.use_x_api_key,
        payload=payload,
        timeout=args.timeout,
        verbose=args.verbose,
    )
    runtime.print_json(data, args.compact)


def cmd_tables_identity_clear(args: argparse.Namespace, *, runtime: TableCommandRuntime) -> None:
    token = runtime.resolve_token(args.token, required=True)
    payload: Dict[str, Any] = {"metadata": {"ingest_identity": {"rules": []}}}
    data = runtime.request_api(
        "PATCH",
        f"/api/tables/{args.table_id}",
        base_url=args.base_url,
        token=token,
        use_x_api_key=args.use_x_api_key,
        payload=payload,
        timeout=args.timeout,
        verbose=args.verbose,
    )
    runtime.print_json(data, args.compact)


def register_tables_subcommands(
    subparsers: argparse._SubParsersAction[argparse.ArgumentParser],
    *,
    add_api_common_arguments: Callable[[argparse.ArgumentParser], None],
    add_output_formatting_arguments: Callable[[argparse.ArgumentParser], None],
    handlers: Dict[str, Callable[[argparse.Namespace], None]],
    table_schema_types: Sequence[str],
) -> None:
    pt = subparsers.add_parser("tables", help="Table operations")
    tables_sub = pt.add_subparsers(dest="tables_cmd", required=True)

    ptl = tables_sub.add_parser("list", help="List tables")
    ptl.add_argument("--view-mode", help="Optional view mode filter")
    add_api_common_arguments(ptl)
    ptl.set_defaults(func=handlers["list"])

    ptc = tables_sub.add_parser("create", help="Create a table")
    ptc.add_argument("--name", help="Table name (when not using --data-json/--data-file)")
    ptc.add_argument("--metadata-json", help="Optional table metadata JSON object")
    ptc.add_argument("--metadata-file", help="Optional file with table metadata JSON")
    ptc.add_argument("--data-json", help="Explicit table create payload JSON")
    ptc.add_argument("--data-file", help="Path to table create payload JSON file")
    add_api_common_arguments(ptc)
    ptc.set_defaults(func=handlers["create"])

    ptu = tables_sub.add_parser("update", help="Update a table's name or metadata")
    ptu.add_argument("--table-id", required=True)
    ptu.add_argument("--metadata-json", help="Metadata JSON object to patch onto the table")
    ptu.add_argument("--metadata-file", help="Path to metadata JSON file")
    ptu.add_argument("--data-json", help="Full PATCH payload JSON (overrides --metadata-*)")
    ptu.add_argument("--data-file", help="Path to full PATCH payload JSON file")
    add_api_common_arguments(ptu)
    ptu.set_defaults(func=handlers["update"])

    _MATCH_STRATEGIES = ["email", "domain", "linkedin_url", "external_id", "exact_text"]
    _SOURCES = ["csv", "webhook", "list_builder", "default"]
    _MODES = ["allow", "skip", "upsert"]
    _MERGE_MODES = ["fill_empty", "overwrite_mapped"]

    ptid = tables_sub.add_parser("identity", help="Manage table ingest identity rules")
    identity_sub = ptid.add_subparsers(dest="tables_identity_cmd", required=True)

    ptig = identity_sub.add_parser("get", help="Print the current ingest_identity config for a table")
    ptig.add_argument("--table-id", required=True)
    add_api_common_arguments(ptig)
    ptig.set_defaults(func=handlers["identity_get"])

    ptis = identity_sub.add_parser("set", help="Add or update an identity rule on a table")
    ptis.add_argument("--table-id", required=True)
    ptis.add_argument("--column-key", required=True, help="Column key to use as identity field")
    ptis.add_argument("--rule-id", default=None, help="Rule ID (defaults to --column-key)")
    ptis.add_argument(
        "--match-strategy",
        choices=_MATCH_STRATEGIES,
        required=True,
        help="Match normalisation strategy",
    )
    ptis.add_argument(
        "--source",
        choices=_SOURCES,
        default="default",
        help="Ingest source this rule applies to (default: applies to all)",
    )
    ptis.add_argument(
        "--mode",
        choices=_MODES,
        default="upsert",
        help="Duplicate handling mode: allow / skip / upsert (default: upsert)",
    )
    ptis.add_argument(
        "--merge-mode",
        choices=_MERGE_MODES,
        default="fill_empty",
        help="Cell merge strategy when upserting (default: fill_empty)",
    )
    ptis.add_argument("--disabled", action="store_true", help="Create rule as disabled")
    add_api_common_arguments(ptis)
    ptis.set_defaults(func=handlers["identity_set"])

    ptic = identity_sub.add_parser("clear", help="Remove all ingest_identity config from a table")
    ptic.add_argument("--table-id", required=True)
    add_api_common_arguments(ptic)
    ptic.set_defaults(func=handlers["identity_clear"])

    ptschema = tables_sub.add_parser("schema", help="Print table payload schemas")
    ptschema.add_argument("--type", choices=["all", *table_schema_types], default="all")
    ptschema.add_argument("--out-file", help="Write schema JSON to file")
    add_output_formatting_arguments(ptschema)
    ptschema.set_defaults(func=handlers["schema"])
