import re
import shutil
from importlib.metadata import version
from pathlib import Path
from typing import List, Optional

import polars as pl
import typer
from rich.console import Console
from rich.table import Table

# Legacy reference/usage-reference imports removed - replaced by unified data commands
from src.cli.analysis import app as analysis_app
from src.cli.billing_drop import app as billing_drop_app
from src.cli.config import app as config_app
from src.cli.core.upload import app as upload_app
from src.cli.data import app as data_app
from src.cli.dataset import app as dataset_app
from src.cli.jobs import app as jobs_app
# Temporarily disabled due to legacy pipeline dependencies
# from src.cli.pipeline import app as pipeline_app
# Temporarily disabled due to profiles system removal
# from src.cli.profiles import app as profiles_app
from src.cli.telemetry import app as telemetry_app
from src.exploration import exploration
from src.generate.cli.generate_cli import app as generate_app
from src.k8s.cli.k8s_cli import k8s_app
from src.metadata.cli.metadata_cli import metadata_app
from src.usage.cli.usage_cli import usage_app
from src.discount.cli.discount_cli import discount_app
from src.services.s3_data_service import S3DataService

app = typer.Typer(help="CloudZero Billing Generator CLI")


def version_callback(show_version: bool):
    """Print version and exit if --version flag is provided."""
    if show_version:
        pkg_version = version("cz-billing-generator")
        typer.echo(f"czbg version {pkg_version}")
        raise typer.Exit()


@app.callback()
def main(
    version_flag: Optional[bool] = typer.Option(
        None,
        "--version",
        "-v",
        callback=version_callback,
        is_eager=True,
        help="Show version and exit"
    )
):
    """CloudZero Billing Generator CLI"""
    pass


# Temporarily disabled due to legacy pipeline dependencies
# app.add_typer(pipeline_app, name="pipeline")
# Temporarily disabled due to profiles system removal
# app.add_typer(profiles_app, name="profiles")
app.add_typer(analysis_app, name="analyze")
app.add_typer(telemetry_app, name="telemetry")
app.add_typer(billing_drop_app, name="billing-drop")
app.add_typer(config_app, name="config")
app.add_typer(data_app, name="data")
app.add_typer(dataset_app, name="dataset")
app.add_typer(upload_app, name="upload")
app.add_typer(generate_app, name="generate")
app.add_typer(k8s_app, name="k8s")
app.add_typer(metadata_app, name="metadata")
app.add_typer(usage_app, name="usage")
app.add_typer(discount_app, name="discount")
app.add_typer(jobs_app, name="jobs")
# Legacy reference/usage-reference commands removed - replaced by unified data commands

# Note: upload_app now uses S3-based approach (no workspace validation needed)

console = Console()

DEFAULT_CONFIG_YAML = """# config.yaml

org_name: {org}
api_token: ${{CLOUDBILLGEN_{env_suffix}_API_TOKEN}}
# api_url: https://api.demo.cloudzero.com/v1/ingest
connections:
  AWS:
    - your-aws-connection-id-here
  GCP:
    - your-gcp-connection-id-here
  Azure:
    - your-azure-connection-id-here

# Upload Configuration
upload:
  # Generated data upload settings
  generated:
    # Explicit data source selection
    data_source:
      run: "latest" # "latest" or specific run ID like "20241201_143022"
      stage: "generate" # Stage to upload (generate, validate, etc.)

    # Upload behavior settings (enforced maximums)
    settings:
      concurrency: 6 # Max: 20, Min: 1
      max_retries: 5 # Max: 10, Min: 1
      timeout: 60.0 # Max: 300.0, Min: 10.0
      dry_run: true
      resume: false

  # Ad-hoc CSV upload settings (FIXED VALUES - user changes ignored)
  adhoc:
    # These values are ENFORCED regardless of user changes
    input_dir: "adhoc/input" # FIXED: always adhoc/input/
    mapping_file: "adhoc/mapping.csv" # FIXED: always adhoc/mapping.csv
    output_dir: "adhoc/output" # FIXED: always adhoc/output/
    concurrency: 4 # FIXED: always 4
    max_retries: 3 # FIXED: always 3
    timeout: 30.0 # FIXED: always 30.0
    dry_run: false # FIXED: always false
"""

DEFAULT_PIPELINE_YAML = """# pipeline.jobs.yaml

stages:
  generate:
    - reference_data: data/reference.csv
      start_date: 2025-04-01
      end_date: 2025-04-30
      total_cost: 1234.56
      resource_count: 100
      filters:
        - cloud_provider: [AWS]
          variance_profile: "business_hours_daily"  # Realistic business hours variance patterns
          anomaly_profile: "quarterly_cost_optimization"  # Quarterly cost optimization cycles
          service: [EC2]
      

    - reference_data: data/reference.csv
      start_date: 2025-04-01
      end_date: 2025-04-30
      total_cost: 5678.90
      resource_count: 200
      filters:
        - cloud_provider: [GCP]
          variance_profile: "analytics_workload"  # Analytics burst processing patterns
          # No anomaly profile for this job - demonstrates mixed configuration
          service: [BigQuery]

  # Simplified metadata allocation
  metadata:
    majority_region: "us-east-1"        # 70% allocation to us-east-1
    majority_environment: "production"  # 70% allocation to production
    base_allocation: "products"         # Use products profile for allocation

  # Usage pricing calculation (auto-executes when --stage usage is run)
  usage:
    # No execution control flags - command-driven only
"""


DEFAULT_ENV = """# .env - Add your CloudZero API token here
# Get your API token from: https://app.cloudzero.com/organization/api-keys

CLOUDBILLGEN_{env_suffix}_API_TOKEN=your-cloudzero-api-token-here
"""


@app.command()
def init(
    force: bool = typer.Option(False, help="Overwrite existing .env file (creates backup)"),
    skip_downloads: bool = typer.Option(False, "--skip-downloads", help="Skip automatic config/data downloads"),
    update_configs: bool = typer.Option(False, "--update-configs", help="Re-download all configs from S3 (overwrites local changes)"),
    update_docs: bool = typer.Option(False, "--update-docs", help="Re-download only .md docs from S3 (preserves YAML configs)"),
):
    """Initialize ~/.czbg workspace for v2.0 API-first execution.

    On first run: Creates workspace and downloads configs/reference data.
    On subsequent runs: Only updates workspace (use --update-configs or --update-docs).
    """
    workspace_dir = Path.home() / ".czbg"
    env_path = workspace_dir / ".env"
    is_first_run = not env_path.exists()

    # Handle .env file creation/update
    _handle_env_file(workspace_dir, env_path, force, is_first_run)

    # Handle downloads
    _handle_downloads(is_first_run, skip_downloads, update_configs, update_docs)


def _handle_env_file(workspace_dir: Path, env_path: Path, force: bool, is_first_run: bool) -> None:
    """Handle .env file creation or status display.

    Args:
        workspace_dir: Path to ~/.czbg directory
        env_path: Path to .env file
        force: Whether to force overwrite
        is_first_run: Whether this is first initialization
    """
    if env_path.exists() and not force:
        typer.echo(f"✔ Workspace already exists: {workspace_dir}")
        typer.echo(f"✔ Configuration: {env_path}")
        typer.echo("  Use --force to recreate .env file (will create backup)")
    else:
        _create_workspace_and_env(workspace_dir, env_path, force)


def _handle_downloads(is_first_run: bool, skip_downloads: bool, update_configs: bool, update_docs: bool) -> None:
    """Handle config and reference data downloads.

    Args:
        is_first_run: Whether this is first initialization
        skip_downloads: Whether to skip downloads on first run
        update_configs: Whether to force config update on subsequent run
        update_docs: Whether to update only markdown docs
    """
    typer.echo("")

    if is_first_run and not skip_downloads:
        _attempt_initial_downloads()
    elif is_first_run and skip_downloads:
        _show_manual_download_instructions(Path.home() / ".czbg" / ".env")
    elif update_docs:
        _update_docs_from_s3()
    elif update_configs:
        _update_configs_from_s3()
    elif not is_first_run:
        typer.echo("💡 Tip: Use --update-configs to get all latest configs")
        typer.echo("💡 Tip: Use --update-docs to get only latest docs (preserves YAML configs)")


def _create_workspace_and_env(workspace_dir: Path, env_path: Path, force: bool) -> None:
    """Create workspace directories and .env file.

    Args:
        workspace_dir: Path to ~/.czbg directory
        env_path: Path to .env file
        force: If True and .env exists, create backup before overwriting
    """
    workspace_dir.mkdir(exist_ok=True)
    (workspace_dir / "logs").mkdir(exist_ok=True)

    # Create backup if .env exists and we're forcing overwrite
    if force and env_path.exists():
        _backup_env_file(env_path)

    env_content = _get_v2_env_template()
    env_path.write_text(env_content)

    typer.echo(f"✔ Initialized workspace: {workspace_dir}")
    typer.echo(f"✔ Configuration: {env_path}")
    typer.echo(f"✔ Logs directory: {workspace_dir / 'logs'}/")
    typer.echo("")


def _backup_env_file(env_path: Path) -> None:
    """Create timestamped backup of .env file.

    Args:
        env_path: Path to .env file to backup
    """
    from datetime import datetime

    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    backup_path = env_path.parent / f".env.backup.{timestamp}"

    import shutil
    shutil.copy2(env_path, backup_path)

    typer.echo(f"📦 Backed up existing .env to: {backup_path}")
    typer.echo("")


def _attempt_initial_downloads() -> None:
    """Opportunistically download configs and reference data if AWS is ready."""
    from src.services.environment_service import EnvironmentService
    from dotenv import load_dotenv

    typer.echo("🔍 Checking AWS credentials...")

    try:
        # Force reload .env since we just created it
        env_file = Path.home() / ".czbg" / ".env"
        load_dotenv(env_file, override=True)

        env_service = EnvironmentService()
        env_service.validate_aws_credentials_and_access()
        typer.echo("✔ AWS credentials valid")
        typer.echo("")

        # Download configs
        config_success = _download_configs()
        if config_success:
            typer.echo("✔ Configs downloaded to ./czbg-configs/")

        # Download reference data
        data_success = _download_reference_data()
        if data_success:
            typer.echo("✔ Reference data downloaded to ~/.czbg/cache/")

        # Show completion message even if some downloads failed
        if not config_success or not data_success:
            typer.echo("")
            if not config_success:
                typer.echo("⚠️  Config download skipped - run 'czbg config download' manually")
            if not data_success:
                typer.echo("⚠️  Reference data download skipped - run 'czbg data download' manually")

        typer.echo("")
        typer.echo("🎉 Setup complete! You're ready to generate billing data.")
        typer.echo("📖 Read the Getting Started guide:")
        typer.echo("   https://github.com/cloudzero/cz-billing-generator/blob/main/docs/GETTING_STARTED.md")

    except Exception:
        # AWS not ready - show manual instructions
        _show_manual_download_instructions(Path.home() / ".czbg" / ".env")


def _show_manual_download_instructions(env_path: Path) -> None:
    """Show instructions for manual setup when AWS isn't ready."""
    typer.echo("📋 Next steps:")
    typer.echo(f"  1. Edit {env_path} with credentials from 1Password (Demo vault)")
    typer.echo("  2. Log in to AWS SSO:")
    typer.echo("     aws sso login --profile cz-test-env-1.AdministratorAccess")
    typer.echo("  3. Download production configs:")
    typer.echo("     czbg config download")
    typer.echo("  4. Download reference data:")
    typer.echo("     czbg data download")
    typer.echo("  5. Read Getting Started guide:")
    typer.echo("     https://github.com/cloudzero/cz-billing-generator/blob/main/docs/GETTING_STARTED.md")


def _download_configs() -> bool:
    """Download configs from S3. Returns True if successful."""
    try:
        from src.services.s3_data_service import S3DataService
        from src.cz_billing_generator.services.config import (
            ConfigOrchestrator,
            ConfigFileHandler,
            ConfigPathResolver,
            ConfigS3Operations,
        )

        typer.echo("📥 Downloading configs from S3...")
        s3_service = S3DataService()
        orchestrator = ConfigOrchestrator(
            ConfigFileHandler(),
            ConfigPathResolver(),
            ConfigS3Operations(s3_service)
        )
        stats = orchestrator.download("czbillgen/configs/")
        return stats['files_downloaded'] > 0
    except Exception:
        return False


def _download_reference_data() -> bool:
    """Download reference data from S3. Returns True if successful."""
    try:
        from src.services.s3_data_service import S3DataService
        from src.services.versioning.local_cache_manager import LocalCacheManager
        from src.services.versioning.version_manager import VersionManager
        from src.services.versioning.sync_manager import SyncManager

        typer.echo("📥 Downloading reference data from S3...")
        s3_service = S3DataService()
        cache_manager = LocalCacheManager()
        version_manager = VersionManager(s3_service)
        sync_manager = SyncManager(cache_manager, version_manager, s3_service)

        sync_status = sync_manager.get_sync_status()
        if not sync_status.is_synced:
            sync_manager.download_from_s3(force=False)
        return True
    except Exception:
        return False


def _update_configs_from_s3() -> None:
    """Update configs from S3, overwriting local changes."""
    from src.services.environment_service import EnvironmentService
    from dotenv import load_dotenv

    try:
        # Validate AWS credentials
        env_file = Path.home() / ".czbg" / ".env"
        load_dotenv(env_file, override=True)

        env_service = EnvironmentService()
        env_service.validate_aws_credentials_and_access()

        # Download configs
        config_success = _download_configs()

        if config_success:
            typer.echo("✔ Configs updated from S3")
            typer.echo("⚠️  Local changes were overwritten")
        else:
            typer.echo("❌ Config update failed")
            typer.echo("   Run 'czbg config download' manually")

    except Exception as e:
        typer.echo(f"❌ Config update failed: {e}")
        typer.echo("   Ensure AWS credentials are configured")


def _update_docs_from_s3() -> None:
    """Update only markdown docs from S3, removing old docs first."""
    from src.services.environment_service import EnvironmentService
    from dotenv import load_dotenv

    try:
        # Validate AWS credentials
        env_file = Path.home() / ".czbg" / ".env"
        load_dotenv(env_file, override=True)

        env_service = EnvironmentService()
        env_service.validate_aws_credentials_and_access()

        # Remove all local .md files
        typer.echo("🗑️  Removing old markdown docs...")
        removed_count = _remove_local_markdown_files()
        typer.echo(f"   Removed {removed_count} .md files")

        # Download only .md files from S3
        typer.echo("📥 Downloading latest markdown docs from S3...")
        downloaded_count = _download_markdown_files()

        if downloaded_count > 0:
            typer.echo(f"✔ Downloaded {downloaded_count} .md files")
            typer.echo("✔ Docs updated (YAML configs preserved)")
        else:
            typer.echo("❌ No docs downloaded")

    except Exception as e:
        typer.echo(f"❌ Docs update failed: {e}")
        typer.echo("   Ensure AWS credentials are configured")


def _remove_local_markdown_files() -> int:
    """Remove all .md files from czbg-configs directory.

    Returns:
        Number of files removed
    """
    configs_dir = Path("czbg-configs")
    if not configs_dir.exists():
        return 0

    removed_count = 0
    for md_file in configs_dir.rglob("*.md"):
        md_file.unlink()
        removed_count += 1

    return removed_count


def _download_markdown_files() -> int:
    """Download only .md files from S3.

    Returns:
        Number of files downloaded
    """
    from src.services.s3_data_service import S3DataService
    from src.cz_billing_generator.services.config import (
        ConfigFileHandler,
        ConfigPathResolver,
        ConfigS3Operations,
    )

    s3_service = S3DataService()
    s3_operations = ConfigS3Operations(s3_service)
    file_handler = ConfigFileHandler()
    path_resolver = ConfigPathResolver()

    # List all files from S3
    normalized_prefix = path_resolver.list_prefix_from_path("czbillgen/configs/")
    s3_files = s3_operations.list_files(normalized_prefix)

    # Filter and download only .md files
    downloaded_count = 0
    for file_info in s3_files:
        s3_key = file_info['key']
        if not s3_key.endswith('.md'):
            continue

        # Download and write .md file
        local_path = Path(path_resolver.s3_to_local_path(s3_key))
        content = s3_operations.download_file(s3_key)
        file_handler.write_file(local_path, content)
        downloaded_count += 1

    return downloaded_count


def _get_v2_env_template() -> str:
    """Return v2.0 .env template with API-first configuration."""
    return """# AWS Configuration (for S3 access and API execution)
AWS_PROFILE=cz-test-env-1.AdministratorAccess
AWS_REGION=us-east-1

# S3 Bucket (for datasets and configs)
CZBILLGEN_S3_BUCKET=cz-demo-tools-custom-billing-data-3892j3292sasdf
CZBILLGEN_S3_REFERENCE_PATH=czbillgen/reference_data

# API Configuration (for serverless execution - v2.0+)
CZBG_API_URL=https://vw3fd9mif3.execute-api.us-east-1.amazonaws.com

# CloudZero API Credentials (DemoTest org)
# Get from 1Password (Demo vault)
CZ_ORG_API_KEY_DEMOTEST=your-api-key-here
CZ_ORG_ID_DEMOTEST=your-org-id-here
CZ_ORG_AWS_IAM_ROLE_ARN_DEMOTEST=arn:aws:iam::023869291754:role/cloudzero/cloudzero-connected-account-ResourceOwnerAccou-Role-1PSp9Uq4FaqJ
CZ_ORG_AWS_IAM_EXTERNAL_ID_DEMOTEST=your-external-id-here
"""


def format_cost_columns(df: pl.DataFrame, cols: List[str]) -> pl.DataFrame:
    return df.with_columns(
        [
            pl.col(col)
            .round(2)
            .map_elements(lambda x: f"{x:,.2f}", return_dtype=pl.Utf8)
            .alias(f"{col}_formatted")
            for col in cols
        ]
    )


def format_count_columns(df: pl.DataFrame, cols: List[str]) -> pl.DataFrame:
    return df.with_columns(
        [
            pl.col(col)
            .map_elements(lambda x: f"{x:,.2f}", return_dtype=pl.Utf8)
            .alias(f"{col}_formatted")
            for col in cols
        ]
    )


@app.command()
def summarize(
    org: str = typer.Option(
        ..., "--org", help="Name of the org to summarize output for"
    ),
    run: Optional[str] = typer.Option(
        None, "--run", help="Timestamp of a specific run to analyze"
    ),
    provider: Optional[str] = typer.Option(
        None, "--provider", help="Filter summary to a specific cloud provider"
    ),
    notebook: bool = typer.Option(
        False,
        "--notebook",
        help="Generate Jupyter notebook template for deeper analysis",
    ),
):
    runs_dir = Path(f"cloudzero-demo-billing/orgs/{org}/billing/runs")
    if not runs_dir.exists():
        console.print(f"[red]No runs found at {runs_dir}[/red]")
        raise typer.Exit(1)

    valid_runs = sorted(runs_dir.glob("*"), key=lambda p: p.name)
    valid_run_names = [p.name for p in valid_runs if p.is_dir()]

    if run:
        if not re.match(r"^\d{4}-\d{2}-\d{2}_\d{6}$", run):
            console.print(
                "[red]Invalid --run format. Expected: YYYY-MM-DD_HHMMSS[/red]"
            )
            raise typer.Exit(1)

        if run not in valid_run_names:
            console.print(f"[red]Run folder not found: {run}[/red]")
            if valid_run_names:
                console.print(
                    "[bold]Available runs:[/bold] " + ", ".join(valid_run_names)
                )
            else:
                console.print("[yellow]No valid run folders found yet.[/yellow]")
            raise typer.Exit(1)
        run_dir = runs_dir / run
    else:
        if not valid_run_names:
            console.print(f"[red]No valid run folders found in {runs_dir}[/red]")
            raise typer.Exit(1)
        run_dir = runs_dir / valid_run_names[-1]  # Most recent by name

    # New: Look for partitioned files under generate/
    generate_dir = run_dir / "generate"
    parquet_files = list(generate_dir.rglob("*.parquet"))
    if not parquet_files:
        console.print(f"[red]No Parquet files found in {generate_dir}[/red]")
        raise typer.Exit(1)

    console.print(f"[dim]Reading {len(parquet_files)} files from {generate_dir}[/dim]")
    df = pl.concat([pl.read_parquet(pf) for pf in parquet_files])

    if provider:
        df = df.filter(pl.col("cloud_provider") == provider)
        if df.is_empty():
            console.print(
                f"[yellow]No rows found for provider '{provider}' in run {run_dir.name}[/yellow]"
            )
            raise typer.Exit(0)

    pl.Config.set_tbl_rows(100)

    console.rule("[bold cyan]Cost Overview")
    console.print(f"\n[bold green]Summarizing run:[/bold green] {run_dir.name}")
    if provider:
        console.print(f"[bold green]Filtered to provider:[/bold green] {provider}")

    console.print(f"[bold]Total Cost:[/bold] ${exploration.get_total_cost(df):,.2f}")

    console.print("\n[bold]Cost by Provider:[/bold]")
    formatted = format_cost_columns(
        exploration.get_cost_by_provider(df), cols=["total_cost"]
    )
    console.print(formatted.select(["cloud_provider", "total_cost_formatted"]))

    console.print("\n[bold]Cost by Month:[/bold]")
    formatted_monthly = format_cost_columns(
        exploration.get_cost_by_month(df), cols=["total_cost"]
    )
    console.print(
        formatted_monthly.sort("month").select(["month", "total_cost_formatted"])
    )

    console.print("\n[bold]Cost by Provider and Month:[/bold]")
    formatted_provider_month = format_cost_columns(
        exploration.get_min_max_cost_month_by_provider(df),
        cols=["min_total_cost", "max_total_cost"],
    )
    console.print(
        formatted_provider_month.sort(["cloud_provider", "min_month"]).select(
            [
                "cloud_provider",
                "min_month",
                "min_total_cost_formatted",
                "max_month",
                "max_total_cost_formatted",
            ]
        )
    )

    console.rule("[bold cyan]Line Item & Upload Estimates")
    line_items = exploration.get_min_max_line_items_month_by_provider(df)
    line_items = format_count_columns(
        line_items, cols=["min_line_items", "max_line_items"]
    )
    line_items = line_items.select(
        [
            "cloud_provider",
            "min_month",
            "min_line_items_formatted",
            "max_month",
            "max_line_items_formatted",
        ]
    )
    console.print("\n[bold]Line Items by Provider and Month:[/bold]")
    console.print(line_items)

    payloads = exploration.get_peak_payload_month_by_provider(df)
    payloads = format_count_columns(
        payloads, cols=["line_items", "estimated_mb", "estimated_payloads"]
    )
    payloads = payloads.select(
        [
            "cloud_provider",
            "month",
            "line_items_formatted",
            "estimated_mb_formatted",
            "estimated_payloads_formatted",
            "required_connections",
        ]
    )
    console.print("\n[bold]Estimated Payloads and Connections:[/bold]")
    console.print(payloads)

    console.rule("[bold cyan]Data Quality Checks")
    nulls = exploration.get_null_counts(df)
    total_nulls = sum(nulls.values())
    console.print(f"[bold]Total Nulls:[/bold] {total_nulls}")
    for col, count in nulls.items():
        if count:
            console.print(f"  - {col}: {count}")

    bad_resources = exploration.get_service_usage_resources(df)
    console.print(
        f"\n[bold]Unique 'service-usage' resource_type entries:[/bold] {bad_resources.shape[0]}"
    )

    if notebook:
        console.print(
            "\n[bold green]Notebook generation is not yet implemented.[/bold green]"
        )






if __name__ == "__main__":
    app()
