"""
AtomIngest CLI Tool.

Command-line interface for running pipelines, validating configs,
and managing ingestion operations.
"""

import sys
import logging
from pathlib import Path
from typing import Optional, Any, Dict, List

import click
import yaml

from atomingest import __version__
from atomingest.core.ingester import Ingester
from atomsql import Database


# Configure logging for CLI
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
)
logger = logging.getLogger(__name__)


@click.group()
@click.version_option(version=__version__, prog_name="atomingest")
def main():
    """
    AtomIngest - Metadata-driven data ingestion framework.

    A powerful CLI tool for running data pipelines, validating configurations,
    and managing ingestion operations with fintech-grade security.
    """
    pass


@main.command()
@click.argument("pipeline_file", type=click.Path(exists=True))
@click.argument("db_url")
@click.option(
    "--source-file",
    "-s",
    type=click.Path(exists=True),
    help="Source data file to ingest (CSV, JSON, etc.)",
)
@click.option(
    "--batch-size",
    "-b",
    type=int,
    default=1000,
    help="Batch size for processing records (default: 1000)",
)
@click.option(
    "--dry-run",
    is_flag=True,
    help="Validate pipeline without executing ingestion",
)
@click.option(
    "--verbose",
    "-v",
    is_flag=True,
    help="Enable verbose logging output",
)
@click.option(
    "--stop-on-error",
    is_flag=True,
    help="Stop pipeline execution on first error",
)
@click.option(
    "--max-errors",
    type=int,
    help="Maximum number of errors before stopping",
)
@click.option(
    "--enable-metrics",
    is_flag=True,
    help="Enable Prometheus metrics collection",
)
@click.option(
    "--json-logs",
    is_flag=True,
    help="Output logs in structured JSON format",
)
def run(
    pipeline_file: str,
    db_url: str,
    source_file: Optional[str],
    batch_size: int,
    dry_run: bool,
    verbose: bool,
    stop_on_error: bool,
    max_errors: Optional[int],
    enable_metrics: bool,
    json_logs: bool,
):
    """
    Run a data ingestion pipeline.

    PIPELINE_FILE: Path to YAML pipeline configuration file.
    DB_URL: Database connection URL (e.g., sqlite:///test.db or postgresql://user:pass@host/db).

    Examples:

      # Run a pipeline with default settings
      atomingest run pipeline.yaml sqlite:///data.db -s data.csv

      # Run with custom batch size and verbose logging
      atomingest run pipeline.yaml postgres://localhost/db -s data.csv -b 5000 -v

      # Dry run to validate pipeline
      atomingest run pipeline.yaml sqlite:///test.db --dry-run

      # Enable metrics and JSON logging
      atomingest run pipeline.yaml sqlite:///db.db -s data.csv --enable-metrics --json-logs
    """
    try:
        # Configure logging based on options
        if verbose:
            logging.getLogger().setLevel(logging.DEBUG)

        if json_logs:
            from atomingest.observability.json_logging import (
                configure_structured_logging,
            )

            configure_structured_logging()

        click.echo("🚀 AtomIngest Pipeline Runner")
        click.echo(f"Pipeline: {pipeline_file}")
        click.echo(f"Database: {db_url}")
        if source_file:
            click.echo(f"Source: {source_file}")
        click.echo(f"Batch Size: {batch_size}")

        # Load pipeline configuration
        click.echo("\n📋 Loading pipeline configuration...")

        with open(pipeline_file, "r", encoding="utf-8") as f:
            config = yaml.safe_load(f)

        if dry_run:
            click.echo("\n✅ Pipeline configuration is valid!")
            click.echo(f"Target Table: {config.get('target_table', 'N/A')}")

            if "schema" in config:
                click.echo(f"Schema Fields: {len(config['schema'])}")

            if "normalization" in config:
                click.echo(f"Normalization Steps: {len(config['normalization'])}")

            if "pre_insert_hooks" in config:
                click.echo(f"Pre-Insert Hooks: {len(config['pre_insert_hooks'])}")

            if "post_insert_hooks" in config:
                click.echo(f"Post-Insert Hooks: {len(config['post_insert_hooks'])}")
            return

        # Run ingestion
        if not source_file:
            click.echo("⚠️  No source file specified. Use -s/--source-file option.")
            click.echo("Pipeline loaded successfully. Use --dry-run to validate only.")
            return

        click.echo("\n⚙️  Starting ingestion...")

        # Create database connection
        db = Database(db_url)

        try:
            ingester = Ingester(db)

            # Execute pipeline
            click.echo(f"📊 Processing data from {source_file}...")

            # Run ingestion using config and source paths
            stats = ingester.run(config=pipeline_file, source=source_file)

            # Display results
            click.echo("\n✨ Ingestion Complete!")
            click.echo(f"✅ Records Processed: {stats.processed}")
            click.echo(f"❌ Records Failed: {stats.failed}")
            click.echo(f"📊 Total Rows: {stats.total_rows}")

            if stats.failed > 0:
                click.secho("⚠️  Check logs for error details", fg="yellow", err=True)
                sys.exit(1)

        finally:
            db.close()

    except FileNotFoundError as e:
        click.echo(f"❌ File not found: {e}", err=True)
        sys.exit(1)
    except yaml.YAMLError as e:
        click.echo(f"❌ Invalid YAML configuration: {e}", err=True)
        sys.exit(1)
    except Exception as e:
        click.echo(f"❌ Error: {e}", err=True)
        if verbose:
            import traceback

            traceback.print_exc()
        sys.exit(1)


@main.command()
@click.argument("pipeline_file", type=click.Path(exists=True))
@click.option(
    "--strict",
    is_flag=True,
    help="Enable strict validation mode (fail on warnings)",
)
@click.option(
    "--verbose",
    "-v",
    is_flag=True,
    help="Show detailed validation information",
)
def validate(pipeline_file: str, strict: bool, verbose: bool):
    """
    Validate a pipeline configuration file.

    PIPELINE_FILE: Path to YAML pipeline configuration file.

    Examples:

      # Basic validation
      atomingest validate pipeline.yaml

      # Strict validation (fail on warnings)
      atomingest validate pipeline.yaml --strict

      # Verbose output
      atomingest validate pipeline.yaml -v
    """
    try:
        click.echo(f"🔍 Validating pipeline: {pipeline_file}")

        # Load and parse YAML
        with open(pipeline_file, "r", encoding="utf-8") as f:
            config = yaml.safe_load(f)

        if verbose:
            click.echo("\n📋 Configuration Structure:")
            click.echo(yaml.dump(config, default_flow_style=False, indent=2))

        # Validate required fields
        errors = []
        warnings = []

        if not config:
            errors.append("Empty configuration file")
        else:
            # Check required fields
            required_fields = ["target_table", "schema"]
            for field in required_fields:
                if field not in config:
                    errors.append(f"Missing required field: '{field}'")

            # Validate target_table
            if "target_table" in config:
                if not isinstance(config["target_table"], str):
                    errors.append("'target_table' must be a string")
                elif not config["target_table"].strip():
                    errors.append("'target_table' cannot be empty")

            # Validate schema
            if "schema" in config:
                if not isinstance(config["schema"], dict):
                    errors.append("'schema' must be a dictionary")
                elif not config["schema"]:
                    errors.append("'schema' cannot be empty")
                else:
                    # Validate each field definition
                    for field_name, field_def in config["schema"].items():
                        if not field_name:
                            errors.append("Schema field name cannot be empty")
                        # Field definition can be string or dict
                        if isinstance(field_def, dict):
                            if "type" not in field_def:
                                warnings.append(
                                    f"Field '{field_name}' missing 'type' specification"
                                )

            # Validate normalization steps (optional)
            if "normalization" in config:
                if not isinstance(config["normalization"], list):
                    errors.append("'normalization' must be a list")
                else:
                    for i, step in enumerate(config["normalization"]):
                        if not isinstance(step, dict):
                            errors.append(
                                f"Normalization step {i} must be a dictionary"
                            )
                        elif "step" not in step:
                            errors.append(f"Normalization step {i} missing 'step' type")

            # Validate hooks (optional)
            for hook_type in ["pre_insert_hooks", "post_insert_hooks"]:
                if hook_type in config:
                    if not isinstance(config[hook_type], list):
                        errors.append(f"'{hook_type}' must be a list")

        # Load pipeline to perform deep validation
        try:
            # Validate by attempting to parse as YAML and check structure
            click.echo("\n✅ Pipeline Structure Valid")

            if verbose:
                click.echo("\n📊 Pipeline Details:")
                click.echo(f"  Target Table: {config.get('target_table', 'N/A')}")

                if "schema" in config:
                    click.echo(f"  Schema Fields: {len(config['schema'])}")

                if "normalization" in config:
                    click.echo(f"  Normalization Steps: {len(config['normalization'])}")

                if "pre_insert_hooks" in config:
                    click.echo(f"  Pre-Insert Hooks: {len(config['pre_insert_hooks'])}")

                if "post_insert_hooks" in config:
                    click.echo(
                        f"  Post-Insert Hooks: {len(config['post_insert_hooks'])}"
                    )

                if "schema" in config and config["schema"]:
                    click.echo("\n  Fields:")
                    for field_name in config["schema"].keys():
                        click.echo(f"    - {field_name}")

        except Exception as e:
            errors.append(f"Pipeline loading failed: {e}")

        # Report results
        if errors:
            click.echo("\n❌ Validation Failed:", err=True)
            for error in errors:
                click.echo(f"  • {error}", err=True)
            sys.exit(1)

        if warnings:
            click.echo("\n⚠️  Warnings:")
            for warning in warnings:
                click.secho(f"  • {warning}", fg="yellow")

            if strict:
                click.echo("\n❌ Strict mode: Failing due to warnings", err=True)
                sys.exit(1)

        if not errors and not warnings:
            click.echo("\n✅ Perfect! No issues found.")
        else:
            click.echo("\n✅ Validation passed (with warnings)")

    except FileNotFoundError:
        click.echo(f"❌ File not found: {pipeline_file}", err=True)
        sys.exit(1)
    except yaml.YAMLError as e:
        click.echo(f"❌ Invalid YAML syntax: {e}", err=True)
        sys.exit(1)
    except Exception as e:
        click.echo(f"❌ Validation error: {e}", err=True)
        sys.exit(1)


@main.command()
@click.argument("output_file", type=click.Path(), required=False)
@click.option(
    "--table-name",
    "-t",
    default="my_table",
    help="Target table name (default: my_table)",
)
@click.option(
    "--with-normalization",
    is_flag=True,
    help="Include sample normalization steps",
)
@click.option(
    "--with-hooks",
    is_flag=True,
    help="Include sample hooks",
)
def init(
    output_file: Optional[str],
    table_name: str,
    with_normalization: bool,
    with_hooks: bool,
):
    """
    Generate a sample pipeline configuration file.

    OUTPUT_FILE: Path to output file (default: pipeline.yaml).

    Examples:

      # Generate basic pipeline config
      atomingest init

      # Generate with custom table name
      atomingest init -t users

      # Generate with all features
      atomingest init pipeline.yaml --with-normalization --with-hooks
    """
    if not output_file:
        output_file = "pipeline.yaml"

    config: Dict[str, Any] = {
        "target_table": table_name,
        "schema": {
            "name": {"type": "CharField", "max_length": 255},
            "email": "EmailField",
            "age": "IntegerField",
            "created_at": "DateTimeField",
        },
    }

    if with_normalization:
        normalization_steps: List[Dict[str, Any]] = [
            {
                "step": "TrimWhitespaceStep",
                "fields": ["name", "email"],
            },
            {
                "step": "LowercaseStep",
                "fields": ["email"],
            },
            {
                "step": "TypeConversionStep",
                "field": "age",
                "target_type": "int",
            },
        ]
        config["normalization"] = normalization_steps

    if with_hooks:
        pre_hooks: List[Dict[str, Any]] = [
            {
                "type": "ValidationHook",
                "name": "validate_email",
                "config": {
                    "field": "email",
                    "pattern": "^[\\w\\.-]+@[\\w\\.-]+\\.\\w+$",
                },
            }
        ]
        post_hooks: List[Dict[str, Any]] = [
            {
                "type": "LoggingHook",
                "name": "log_insert",
                "config": {"message": "Record inserted successfully"},
            }
        ]
        config["pre_insert_hooks"] = pre_hooks
        config["post_insert_hooks"] = post_hooks

    try:
        with open(output_file, "w", encoding="utf-8") as f:
            yaml.dump(config, f, default_flow_style=False, indent=2, sort_keys=False)

        click.echo(f"✅ Created sample pipeline: {output_file}")
        click.echo("\n📋 Configuration:")
        click.echo(yaml.dump(config, default_flow_style=False, indent=2))
        click.echo("\n💡 Next steps:")
        click.echo(f"  1. Edit {output_file} to match your data structure")
        click.echo(f"  2. Validate: atomingest validate {output_file}")
        click.echo(f"  3. Run: atomingest run {output_file} <db_url> -s <data_file>")

    except Exception as e:
        click.echo(f"❌ Error creating file: {e}", err=True)
        sys.exit(1)


@main.command(name="list")
@click.argument("directory", type=click.Path(exists=True), required=False)
@click.option(
    "--verbose",
    "-v",
    is_flag=True,
    help="Show detailed pipeline information",
)
def list_pipelines(directory: Optional[str], verbose: bool):
    """
    List available pipeline configuration files.

    DIRECTORY: Directory to search for pipeline files (default: current directory).

    Examples:

      # List pipelines in current directory
      atomingest list

      # List pipelines in specific directory
      atomingest list ./pipelines

      # List with detailed information
      atomingest list -v
    """
    if not directory:
        directory = "."

    try:
        search_path = Path(directory)

        # Find all YAML files
        yaml_files = list(search_path.glob("*.yaml")) + list(search_path.glob("*.yml"))

        if not yaml_files:
            click.echo(f"📁 No pipeline files found in {directory}")
            return

        click.echo(f"📁 Found {len(yaml_files)} pipeline file(s) in {directory}:\n")

        for yaml_file in yaml_files:
            click.echo(f"  📄 {yaml_file.name}")

            if verbose:
                try:
                    with open(yaml_file, "r", encoding="utf-8") as f:
                        config = yaml.safe_load(f)

                    if config and isinstance(config, dict):
                        if "target_table" in config:
                            click.echo(f"     Table: {config['target_table']}")
                        if "schema" in config:
                            click.echo(f"     Fields: {len(config['schema'])}")
                        if "normalization" in config:
                            click.echo(
                                f"     Normalization Steps: {len(config['normalization'])}"
                            )

                except Exception as e:
                    click.secho(f"     ⚠️  Error reading file: {e}", fg="yellow")

                click.echo()

    except Exception as e:
        click.echo(f"❌ Error listing files: {e}", err=True)
        sys.exit(1)


@main.command()
@click.option(
    "--format",
    "-f",
    type=click.Choice(["text", "json"], case_sensitive=False),
    default="text",
    help="Output format (default: text)",
)
def version(format: str):
    """
    Display version information.

    Examples:

      # Show version
      atomingest version

      # Show version in JSON format
      atomingest version --format json
    """
    version_info = {
        "version": __version__,
        "python_requires": ">=3.12",
        "framework": "AtomIngest",
        "description": "Metadata-driven data ingestion framework",
    }

    if format == "json":
        import json

        click.echo(json.dumps(version_info, indent=2))
    else:
        click.echo(f"AtomIngest v{version_info['version']}")
        click.echo(f"Python: {version_info['python_requires']}")
        click.echo(f"{version_info['description']}")


if __name__ == "__main__":
    main()
