"""Command-line interface for the Civic Exchange Protocol.

This module provides CLI commands for:
- snfei: Generate an SNFEI for an entity name and country
- version: Display the package version
- validate-json: Validate JSON files against CEP schemas
- codegen-rust: Generate Rust types from CEP JSON Schemas
- generate-example: Generate example data files from raw sources.

For example:

uv run cx codegen-rust
uv run cx codegen-python-constants
uv run cx generate-example examples/entity
uv run cx generate-example examples/entity --overwrite
"""

from importlib.metadata import PackageNotFoundError, version
import json
from pathlib import Path
from typing import Annotated, Any

import typer

from civic_interconnect.cep.adapters.demo_entities import (
    extract_example_entity_inputs,
    find_example_slices,
    load_raw_source,
)
from civic_interconnect.cep.codegen.python_constants import (
    DEFAULT_ENTITY_CONSTANTS_OUT,
    write_entity_constants,
)
from civic_interconnect.cep.codegen.python_constants import (
    DEFAULT_ENTITY_SCHEMA as DEFAULT_ENTITY_SCHEMA_FOR_CONSTANTS,
)
from civic_interconnect.cep.codegen.rust_generated import write_generated_rust
from civic_interconnect.cep.entity.api import build_entity_from_raw
from civic_interconnect.cep.snfei import generate_snfei_detailed
from civic_interconnect.cep.validation.json_validator import (
    ValidationSummary,
    validate_json_path,
)

app = typer.Typer(help="Civic Exchange Protocol CLI")

DEFAULT_ENTITY_SCHEMA = Path("schemas/core/cep.entity.schema.json")
DEFAULT_RELATIONSHIP_SCHEMA = Path("schemas/core/cep.relationship.schema.json")
DEFAULT_EXCHANGE_SCHEMA = Path("schemas/core/cep.exchange.schema.json")

DEFAULT_ENTITY_OUT = Path("crates/cep-core/src/entity/generated.rs")
DEFAULT_RELATIONSHIP_OUT = Path("crates/cep-core/src/relationship/generated.rs")
DEFAULT_EXCHANGE_OUT = Path("crates/cep-core/src/exchange/generated.rs")


@app.command("codegen-python-constants")
def codegen_python_constants(
    entity_schema: Path | None = None,
    entity_out: Path | None = None,
) -> None:
    """Generate Python field-name constants from CEP JSON Schemas.

    Currently generates:

    - civic_interconnect.cep.constants.entity_fields
    """
    if entity_schema is None:
        entity_schema = DEFAULT_ENTITY_SCHEMA_FOR_CONSTANTS
    if entity_out is None:
        entity_out = DEFAULT_ENTITY_CONSTANTS_OUT

    write_entity_constants(entity_schema, entity_out)
    typer.echo(f"Wrote {entity_out}")


@app.command("codegen-rust")
def codegen_rust(
    entity_schema: Path | None = None,
    relationship_schema: Path | None = None,
    exchange_schema: Path | None = None,
    entity_out: Path | None = None,
    relationship_out: Path | None = None,
    exchange_out: Path | None = None,
) -> None:
    """Generate Rust types from CEP JSON Schemas into generated.rs files."""
    if entity_schema is None:
        entity_schema = DEFAULT_ENTITY_SCHEMA
    if relationship_schema is None:
        relationship_schema = DEFAULT_RELATIONSHIP_SCHEMA
    if exchange_schema is None:
        exchange_schema = DEFAULT_EXCHANGE_SCHEMA
    if entity_out is None:
        entity_out = DEFAULT_ENTITY_OUT
    if relationship_out is None:
        relationship_out = DEFAULT_RELATIONSHIP_OUT
    if exchange_out is None:
        exchange_out = DEFAULT_EXCHANGE_OUT

    # Adjust struct names here as needed
    write_generated_rust(entity_schema, "EntityRecord", entity_out)
    write_generated_rust(relationship_schema, "RelationshipRecord", relationship_out)
    write_generated_rust(exchange_schema, "ExchangeRecord", exchange_out)

    typer.echo(f"Wrote {entity_out}")
    typer.echo(f"Wrote {relationship_out}")
    typer.echo(f"Wrote {exchange_out}")


@app.command()
def snfei(
    legal_name: str = typer.Argument(..., help="Raw legal name"),
    country_code: str = typer.Option("US", "--country-code", "-c", help="ISO country code"),
) -> None:
    """Generate an SNFEI for an entity name and country."""
    result = generate_snfei_detailed(
        legal_name=legal_name,
        country_code=country_code,
        address=None,
        registration_date=None,
        lei=None,
        sam_uei=None,
    )
    snfei_value = result["snfei"]
    tier = result["tier"]
    confidence = result["confidenceScore"]

    typer.echo(f"SNFEI: {snfei_value}")
    typer.echo(f"Tier: {tier}, confidence: {confidence}")


@app.command()
def version_cmd() -> None:
    """Show package version."""
    try:
        v = version("civic-interconnect")
    except PackageNotFoundError:
        v = "0.0.0"
    typer.echo(v)


@app.command()
def validate_json(
    path: Path | None = None,
    schema: str = typer.Option(
        ...,
        "--schema",
        "-s",
        help="Schema name (for example: entity, exchange, relationship, snfei).",
    ),
    recursive: bool = typer.Option(
        False,
        "--recursive",
        "-r",
        help="Recurse into subdirectories when validating a directory.",
    ),
) -> None:
    """Validate JSON file(s) against a CEP JSON Schema.

    Behavior:
    - If PATH is a file, validates that single JSON file.
    - If PATH is a directory, validates all *.json files within it.
      Use --recursive to walk subdirectories.
    """
    if path is None:
        typer.echo("Error: Path argument is required.")
        raise typer.Exit(code=1)

    summary: ValidationSummary = validate_json_path(
        path=path,
        schema_name=schema,
        recursive=recursive,
    )

    if not summary.results:
        typer.echo("No JSON files found to validate.")
        raise typer.Exit(code=1)

    errors_found = False

    for result in summary.results:
        if result.ok:
            typer.echo(f"[OK] {result.path}")
        else:
            errors_found = True
            typer.echo(f"[ERROR] {result.path}")
            for err in result.errors:
                typer.echo(f"  - {err}")

    if errors_found:
        typer.echo("Validation completed with errors.")
        raise typer.Exit(code=1)

    typer.echo("All files validated successfully.")
    raise typer.Exit(code=0)


def _write_json(path: Path, data: Any) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    with path.open("w", encoding="utf-8") as f:
        json.dump(data, f, indent=2, sort_keys=True)
        f.write("\n")


@app.command("generate-example")
def generate_example(
    path: Annotated[
        Path,
        typer.Argument(
            help=(
                "Path to an example slice or a directory containing slices "
                "(e.g. examples/entity or examples/entity/municipality/us_il_01)"
            ),
        ),
    ],
    overwrite: Annotated[
        bool,
        typer.Option(
            "--overwrite",
            help="Overwrite existing 02/03/04 files instead of skipping them.",
        ),
    ] = False,
) -> None:
    """Generate 02_normalized, 03_canonical, and 04_entity_record JSON files.

    This walks any examples under `path` that contain 01_raw_source.json and
    runs the full pipeline:

        raw -> normalized -> canonical -> EntityRecord
    """
    slices = find_example_slices(path)
    if not slices:
        typer.echo(f"No example slices found under {path}")
        raise typer.Exit(code=1)

    typer.echo(f"Found {len(slices)} example slice(s) under {path}")
    for slice_dir in slices:
        typer.echo(f"\n[example] {slice_dir}")

        f02 = slice_dir / "02_normalized.json"
        f03 = slice_dir / "03_canonical.json"
        f04 = slice_dir / "04_entity_record.json"

        if not overwrite and f02.exists() and f03.exists() and f04.exists():
            typer.echo("  - 02/03/04 already exist, skipping (use --overwrite to regenerate)")
            continue

        # Load raw
        try:
            raw = load_raw_source(slice_dir)
        except (FileNotFoundError, ValueError) as exc:
            typer.echo(f"  ! {exc}, skipping slice")
            continue

        # Map messy raw shapes into the normalized fields we need
        try:
            inputs = extract_example_entity_inputs(raw, slice_dir)
        except KeyError as exc:
            typer.echo(f"  ! missing required raw field(s): {exc!r}, skipping slice")
            continue

        jurisdiction_iso = inputs.jurisdiction_iso
        legal_name = inputs.legal_name
        country_code = inputs.country_code
        entity_type = inputs.entity_type
        address = inputs.address
        registration_date = inputs.registration_date

        # 1) Canonical input (normalizing functor) + SNFEI
        snfei_result = generate_snfei_detailed(
            legal_name=legal_name,
            country_code=country_code,
            address=address,
            registration_date=registration_date,
            lei=None,
            sam_uei=None,
        )
        # Rust returns:
        # {
        #   "snfei": {"value": "..."},
        #   "canonical": {
        #       "legalNameNormalized": "...",
        #       "addressNormalized": "...",
        #       "countryCode": "...",
        #       "registrationDate": "..." | null
        #   },
        #   ...
        # }

        snfei_value = snfei_result["snfei"]["value"]
        canonical_raw = snfei_result.get("canonical", {})
        # Be tolerant to either camelCase or snake_case field names
        legal_name_normalized = (
            canonical_raw.get("legalNameNormalized")
            or canonical_raw.get("legal_name_normalized")
            or ""
        )
        address_normalized = canonical_raw.get("addressNormalized") or canonical_raw.get(
            "address_normalized"
        )
        country_code_canonical = (
            canonical_raw.get("countryCode") or canonical_raw.get("country_code") or country_code
        )
        registration_date_canonical = canonical_raw.get("registrationDate") or canonical_raw.get(
            "registration_date"
        )

        # 2) NormalizedEntityInput (02_normalized.json)
        normalized: dict[str, Any] = {
            "jurisdictionIso": jurisdiction_iso,
            "legalName": legal_name,
            "legalNameNormalized": legal_name_normalized,
            "snfei": snfei_value,
            "entityType": entity_type,
        }
        _write_json(f02, normalized)
        typer.echo(f"  - wrote {f02.name}")

        # 3) Canonical snapshot (03_canonical.json)
        canonical_json: dict[str, Any] = {
            "legalNameNormalized": legal_name_normalized,
            "addressNormalized": address_normalized,
            "countryCode": country_code_canonical,
            "registrationDate": registration_date_canonical,
        }
        _write_json(f03, canonical_json)
        typer.echo(f"  - wrote {f03.name}")

        # 4) Final EntityRecord via builder (04_entity_record.json)
        entity_record = build_entity_from_raw(normalized)
        _write_json(f04, entity_record)
        typer.echo(f"  - wrote {f04.name}")
