"""CLI command implementations for ``pycharter etl`` subcommands.

Each public function corresponds to one CLI sub-command and returns an
``int`` exit code (0 = success, 1 = failure).
"""

from __future__ import annotations

import asyncio
import logging
import shutil
import sys
from pathlib import Path
from typing import Any

import yaml

logger = logging.getLogger(__name__)

# Mapping from --template preset to (dest filename -> source template filename).
# settings.yaml and variables.yaml are always included.
_INIT_PRESETS: dict[str, dict[str, str]] = {
    "http-to-postgres": {
        "extract.yaml": "extract_http_simple.yaml",
        "transform.yaml": "transform_simple.yaml",
        "load.yaml": "load_postgresql.yaml",
    },
    "database-incremental": {
        "extract.yaml": "extract_database_incremental.yaml",
        "transform.yaml": "transform_simple.yaml",
        "load.yaml": "load_postgresql.yaml",
    },
    "file-to-file": {
        "extract.yaml": "extract_file_csv.yaml",
        "transform.yaml": "transform_simple.yaml",
        "load.yaml": "load_file.yaml",
    },
}


def _parse_key_values(items: list[str]) -> dict[str, str]:
    """Parse ``KEY=VALUE`` pairs from a list of strings."""
    out: dict[str, str] = {}
    for item in items:
        if "=" not in item:
            print(f"Invalid KEY=VALUE pair: {item}", file=sys.stderr)
            continue
        key, value = item.split("=", 1)
        out[key.strip()] = value.strip()
    return out


def _templates_dir() -> Path:
    """Return the path to the bundled ETL templates directory.

    Resolves from the pycharter package root (``pycharter/__init__.py``).
    """
    import pycharter

    pkg_root = Path(pycharter.__file__).resolve().parent
    return pkg_root / "data" / "templates" / "etl"


# =========================================================================
# pycharter etl run
# =========================================================================


def cmd_etl_run(
    path: str,
    variables: list[str] | None = None,
    params: list[str] | None = None,
    dry_run: bool = False,
    validate: bool = True,
) -> int:
    """Run an ETL pipeline from a directory or file.

    Args:
        path: Pipeline directory or config file.
        variables: ``KEY=VALUE`` pairs for ``${VAR}`` substitution.
        params: ``KEY=VALUE`` pairs passed to extractor/loader.
        dry_run: Extract and transform only (skip loading).
        validate: Validate config before running.

    Returns:
        Exit code (0 on success, 1 on failure).
    """
    from pycharter.etl_generator.pipeline import Pipeline

    target = Path(path)
    vars_dict = _parse_key_values(variables or [])
    params_dict = _parse_key_values(params or [])

    try:
        if target.is_dir():
            pipeline = Pipeline.from_config_dir(
                target, variables=vars_dict, validate=validate
            )
        elif target.is_file():
            pipeline = Pipeline.from_config_file(
                target, variables=vars_dict, validate=validate
            )
        else:
            print(f"Path not found: {path}", file=sys.stderr)
            return 1

        result = asyncio.run(pipeline.run(dry_run=dry_run, **params_dict))
    except Exception as exc:
        print(f"Error: {exc}", file=sys.stderr)
        return 1

    # Print result summary
    print(f"Pipeline: {result.pipeline_name or 'unnamed'}")
    print(f"Run ID:   {result.run_id}")
    print(f"Status:   {'SUCCESS' if result.success else 'FAILED'}")
    print(f"Duration: {result.duration_seconds:.2f}s")
    print(f"Rows extracted:   {result.rows_extracted}")
    print(f"Rows transformed: {result.rows_transformed}")
    print(f"Rows loaded:      {result.rows_loaded}")
    if result.rows_failed:
        print(f"Rows failed:      {result.rows_failed}")
    if result.errors:
        print("Errors:")
        for err in result.errors:
            print(f"  - {err}")

    return 0 if result.success else 1


# =========================================================================
# pycharter etl validate
# =========================================================================


def cmd_etl_validate(path: str) -> int:
    """Validate pipeline config files without running.

    Args:
        path: Pipeline directory or config file.

    Returns:
        Exit code (0 if valid, 1 if invalid).
    """
    from pycharter.etl_generator.pipeline import Pipeline

    target = Path(path)

    try:
        if target.is_dir():
            Pipeline.from_config_dir(target, validate=True)
        elif target.is_file():
            Pipeline.from_config_file(target, validate=True)
        else:
            print(f"Path not found: {path}", file=sys.stderr)
            return 1
    except Exception as exc:
        print(f"Validation FAILED: {exc}", file=sys.stderr)
        return 1

    print("Validation OK")
    return 0


# =========================================================================
# pycharter etl init
# =========================================================================


def cmd_etl_init(path: str, template: str = "http-to-postgres") -> int:
    """Scaffold a new pipeline directory from a template preset.

    Args:
        path: Directory to create.
        template: Preset name (see ``_INIT_PRESETS``).

    Returns:
        Exit code (0 on success, 1 on failure).
    """
    preset = _INIT_PRESETS.get(template)
    if not preset:
        print(
            f"Unknown template: {template}. " f"Available: {', '.join(_INIT_PRESETS)}",
            file=sys.stderr,
        )
        return 1

    target = Path(path)
    target.mkdir(parents=True, exist_ok=True)

    templates = _templates_dir()

    created: list[str] = []

    # Copy preset files
    for dest_name, src_name in preset.items():
        src = templates / src_name
        dst = target / dest_name
        if not src.exists():
            print(f"Warning: template {src_name} not found, skipping", file=sys.stderr)
            continue
        shutil.copy2(src, dst)
        created.append(str(dst))

    # Always copy settings.yaml and variables.yaml
    for shared in ("settings.yaml", "variables.yaml"):
        src = templates / shared
        dst = target / shared
        if src.exists():
            shutil.copy2(src, dst)
            created.append(str(dst))

    print(f"Created pipeline from '{template}' template:")
    for f in created:
        print(f"  {f}")

    return 0


# =========================================================================
# pycharter etl list
# =========================================================================


def cmd_etl_list(path: str = ".") -> int:
    """List pipelines discovered in a directory tree.

    Args:
        path: Root directory to scan.

    Returns:
        Exit code (0 on success, 1 on failure).
    """
    from pycharter.etl_generator.factory import PipelineFactory

    target = Path(path).resolve()
    if not target.is_dir():
        print(f"Not a directory: {path}", file=sys.stderr)
        return 1

    factory = PipelineFactory(config_root=str(target))
    names = factory.get_pipeline_names()

    if not names:
        # Also check if target itself is a pipeline dir
        if (target / "extract.yaml").exists() and (target / "load.yaml").exists():
            _print_pipeline_row(target.name, str(target))
            return 0
        print("No pipelines found.")
        return 0

    print(f"{'Name':<30} {'Path'}")
    print("-" * 70)
    for name in names:
        pipeline_dir = factory.get_contract_dir(name)
        print(f"{name:<30} {pipeline_dir}")

    print(f"\n{len(names)} pipeline(s) found.")
    return 0


def _print_pipeline_row(name: str, path: str) -> None:
    """Print a single pipeline row for list output."""
    print(f"{'Name':<30} {'Path'}")
    print("-" * 70)
    print(f"{name:<30} {path}")
    print("\n1 pipeline(s) found.")


# =========================================================================
# pycharter etl inspect
# =========================================================================


def cmd_etl_inspect(path: str) -> int:
    """Show parsed config summary for a pipeline.

    Args:
        path: Pipeline directory or config file.

    Returns:
        Exit code (0 on success, 1 on failure).
    """
    target = Path(path)

    try:
        config = _load_inspect_config(target)
    except Exception as exc:
        print(f"Error loading config: {exc}", file=sys.stderr)
        return 1

    _print_inspect_summary(config, target)
    return 0


def _load_inspect_config(target: Path) -> dict[str, Any]:
    """Load configs from a pipeline path into a summary dict."""
    config: dict[str, Any] = {}

    if target.is_dir():
        config["name"] = target.name
        for fname in ("extract.yaml", "transform.yaml", "load.yaml", "settings.yaml"):
            fpath = target / fname
            if fpath.exists():
                with open(fpath) as f:
                    config[fname.replace(".yaml", "")] = yaml.safe_load(f) or {}
    elif target.is_file():
        with open(target) as f:
            raw = yaml.safe_load(f) or {}
        config["name"] = raw.get("name", target.stem)
        config["extract"] = raw.get("extract", {})
        config["transform"] = raw.get("transform")
        config["load"] = raw.get("load", {})
        config["settings"] = {
            k: v
            for k, v in raw.items()
            if k not in ("name", "extract", "transform", "load", "variables")
        }
    else:
        raise FileNotFoundError(f"Path not found: {target}")

    return config


def _print_inspect_summary(config: dict[str, Any], target: Path) -> None:
    """Print a human-readable pipeline config summary."""
    print(f"Pipeline: {config.get('name', 'unnamed')}")
    print(f"Path:     {target.resolve()}")
    print()

    # Extract
    extract = config.get("extract", {})
    if extract:
        print("Extract:")
        print(f"  Type: {extract.get('type', 'unknown')}")
        if extract.get("url"):
            print(f"  URL:  {extract['url']}")
        if extract.get("query"):
            query = extract["query"].strip()
            if len(query) > 80:
                query = query[:77] + "..."
            print(f"  Query: {query}")
        if extract.get("path"):
            print(f"  Path: {extract['path']}")
        inc = extract.get("incremental")
        if inc:
            print(
                f"  Incremental: field={inc.get('watermark_field')}, "
                f"initial={inc.get('initial_value', 'none')}"
            )
        print()

    # Transform
    transform = config.get("transform")
    if transform:
        print("Transform:")
        if isinstance(transform, list):
            for op in transform:
                if isinstance(op, dict):
                    print(f"  - {', '.join(op.keys())}")
                else:
                    print(f"  - {op}")
        elif isinstance(transform, dict):
            inner = transform.get("transform", transform)
            if isinstance(inner, dict):
                for key in inner:
                    print(f"  - {key}")
            elif isinstance(inner, list):
                for op in inner:
                    if isinstance(op, dict):
                        print(f"  - {', '.join(op.keys())}")
        print()

    # Load
    load = config.get("load", {})
    if load:
        print("Load:")
        print(f"  Type:  {load.get('type', 'unknown')}")
        if load.get("table"):
            print(f"  Table: {load['table']}")
        if load.get("path"):
            print(f"  Path:  {load['path']}")
        if load.get("write_method"):
            print(f"  Write: {load['write_method']}")
        print()

    # Settings
    settings = config.get("settings", {})
    if settings:
        print("Settings:")
        if settings.get("state_store"):
            ss = settings["state_store"]
            print(
                f"  State Store: backend={ss.get('backend', 'file')}, "
                f"path={ss.get('path', './.state')}"
            )
        if settings.get("dlq"):
            dlq = settings["dlq"]
            print(
                f"  DLQ: backend={dlq.get('backend', 'file')}, "
                f"enabled={dlq.get('enabled', True)}"
            )
        if settings.get("plugins"):
            plugins = settings["plugins"]
            ext_count = len(plugins.get("extractors") or {})
            ldr_count = len(plugins.get("loaders") or {})
            print(f"  Plugins: {ext_count} extractor(s), {ldr_count} loader(s)")
