import sys
from datetime import datetime, timezone
from pathlib import Path
from typing import List, Optional

import botocore.exceptions
import click
import yaml

# ------------------------
# Config + filtering
# ------------------------
from cleancloud.config.schema import (
    CleanCloudConfig,
    IgnoreTagRuleConfig,
    load_config,
)
from cleancloud.core.confidence import ConfidenceLevel
from cleancloud.core.finding import Finding
from cleancloud.filtering.tags import (
    compile_rules,
    filter_findings_by_tags,
)
from cleancloud.output.csv import write_csv
from cleancloud.output.human import print_human
from cleancloud.output.json import write_json
from cleancloud.output.markdown import write_markdown
from cleancloud.output.summary import _print_summary, build_summary
from cleancloud.policy.exit_policy import (
    CONFIDENCE_ORDER,
    EXIT_ERROR,
    EXIT_PERMISSION_ERROR,
    EXIT_POLICY_VIOLATION,
    determine_exit_code,
)
from cleancloud.providers.aws.scan import scan_aws_with_region_selection
from cleancloud.providers.azure.scan import scan_azure_with_region_selection


@click.command("scan")
@click.option(
    "--provider", required=True, type=click.Choice(["aws", "azure"]), help="Cloud provider to scan"
)
@click.option(
    "--region", default=None, help="Specific region to scan (AWS region or Azure location)"
)
@click.option(
    "--all-regions",
    is_flag=True,
    help="Scan all regions with resources (auto-detects active regions)",
)
@click.option(
    "--subscription",
    multiple=True,
    help="Azure subscription ID to scan (can specify multiple times)",
)
@click.option(
    "--all-subscriptions",
    is_flag=True,
    help="Scan all accessible Azure subscriptions (default behavior)",
)
@click.option("--profile", default=None, help="AWS CLI profile name")
@click.option(
    "--output",
    default="human",
    type=click.Choice(["human", "json", "csv", "markdown"]),
)
@click.option(
    "--output-file",
    default=None,
    help="Output file path (required for json/csv; optional for markdown — prints to stdout if omitted)",
)
@click.option(
    "--fail-on-findings",
    is_flag=True,
    help="Exit with non-zero code if findings are detected",
)
@click.option(
    "--fail-on-confidence",
    type=click.Choice(["LOW", "MEDIUM", "HIGH"]),
    default=None,
    help="Fail scan if findings at or above this confidence exist",
)
@click.option(
    "--config",
    type=click.Path(exists=True),
    help="Path to cleancloud.yaml",
)
@click.option(
    "--ignore-tag",
    multiple=True,
    help="Ignore findings by tag (key or key:value). Overrides config.",
)
@click.option(
    "--fail-on-cost",
    type=float,
    default=None,
    help="Fail scan if estimated monthly waste exceeds this USD amount",
)
@click.option(
    "--no-feedback",
    is_flag=True,
    default=False,
    help="Disable post-scan feedback prompt (recommended for CI/CD runs)",
)
def scan(
    provider: str,
    region: Optional[str],
    all_regions: bool,
    subscription: tuple,
    all_subscriptions: bool,
    profile: Optional[str],
    output: str,
    output_file: Optional[str],
    fail_on_findings: bool,
    fail_on_confidence: Optional[str],
    fail_on_cost: Optional[float],
    config: Optional[str],
    ignore_tag: List[str],
    no_feedback: bool,
):
    click.echo()
    click.echo("Starting CleanCloud scan...")
    click.echo()
    click.echo(f"Provider: {provider}")
    click.echo()

    try:

        findings: List[Finding] = []
        skipped_rules: List[dict] = []

        # Provider-specific metadata
        region_selection_mode = None
        regions_scanned = []
        subscription_selection_mode = None
        subscriptions_scanned = []

        if provider == "aws":
            region_selection_mode, findings, regions_scanned, skipped_rules = (
                scan_aws_with_region_selection(
                    profile=profile, region=region, all_regions=all_regions
                )
            )

        elif provider == "azure":
            # Convert tuple to list for Azure
            subscription_list = list(subscription) if subscription else None
            subscription_selection_mode, findings, subscriptions_scanned, skipped_rules = (
                scan_azure_with_region_selection(
                    region=region,
                    subscriptions=subscription_list,
                    all_subscriptions=all_subscriptions,
                )
            )
            # Extract unique regions from findings
            regions_scanned = sorted(set(f.region for f in findings if f.region))

        ignored_count = 0
        rules = []

        cfg = CleanCloudConfig.empty()
        if config:
            with open(config) as f:
                raw = yaml.safe_load(f) or {}
                cfg = load_config(raw)

        # CLI overrides config
        if ignore_tag:
            rules = compile_rules(
                [
                    IgnoreTagRuleConfig(
                        key=item.split(":", 1)[0],
                        value=item.split(":", 1)[1] if ":" in item else None,
                    )
                    for item in ignore_tag
                ]
            )

        elif cfg.tag_filtering and cfg.tag_filtering.enabled:
            rules = compile_rules(cfg.tag_filtering.ignore)

        if rules:
            result = filter_findings_by_tags(findings, rules)
            ignored_count = len(result.ignored)
            findings = result.kept

        summary = build_summary(findings, skipped_rules=skipped_rules)
        summary["scanned_at"] = datetime.now(timezone.utc).isoformat()
        summary["regions_scanned"] = regions_scanned
        summary["provider"] = provider

        # Add provider-specific fields
        if provider == "aws":
            summary["region_selection_mode"] = region_selection_mode
        elif provider == "azure":
            summary["subscription_selection_mode"] = subscription_selection_mode
            summary["subscriptions_scanned"] = subscriptions_scanned
        summary["highest_confidence"] = max(
            (f.confidence for f in findings),
            default=None,
            key=lambda c: CONFIDENCE_ORDER.get(c, 0),
        )
        summary["high_conf_findings"] = len(
            [f for f in findings if f.confidence == ConfidenceLevel.HIGH]
        )

        if ignored_count > 0:
            summary["ignored_by_tag_policy"] = ignored_count

        output_path = Path(output_file) if output_file else None

        if output in ("json", "csv") and not output_path:
            raise ValueError("--output-file is required for json/csv output")

        if output == "json":
            write_json(
                {
                    "schema_version": "1.1.0",
                    "summary": summary,
                    "findings": findings,
                },
                output_path,
            )
            click.echo(f"JSON output written to {output_path}")
            click.echo()

        elif output == "csv":
            write_csv(findings, output_path)
            click.echo(f"CSV output written to {output_path}")
            click.echo()

        elif output == "markdown":
            result = write_markdown(findings, summary, output_path)
            if result is not None:
                click.echo(result)
            else:
                click.echo(f"Markdown output written to {output_path}")
            click.echo()

        else:
            print_human(findings)
            _print_summary(summary, region_selection_mode)

        # Community prompt (all output modes)
        click.echo()
        click.echo(
            "Share your findings: "
            "https://github.com/cleancloud-io/cleancloud/issues/new?template=share_findings.md"
        )
        click.echo(
            "Report a bug: "
            "https://github.com/cleancloud-io/cleancloud/issues/new?template=bug_report.md"
        )
        click.echo()

        # ========================
        # Exit policy
        # ========================
        exit_code = determine_exit_code(
            findings,
            fail_on_findings=fail_on_findings,
            fail_on_confidence=fail_on_confidence,
            fail_on_cost=fail_on_cost,
        )

        if exit_code == EXIT_POLICY_VIOLATION:
            click.echo("\nCleanCloud policy violation detected")

        sys.exit(exit_code)

    except PermissionError as e:
        click.echo(f"Permission error: {e}")
        sys.exit(EXIT_PERMISSION_ERROR)

    except EnvironmentError as e:
        # Raised by create_azure_session() when Azure auth fails
        click.echo()
        click.echo(f"Authentication failed — {e}")
        click.echo()
        click.echo("Run `cleancloud doctor --provider azure` to diagnose.")
        sys.exit(EXIT_PERMISSION_ERROR)

    except botocore.exceptions.NoCredentialsError:
        click.echo()
        click.echo("Authentication failed — no AWS credentials found.")
        click.echo()
        click.echo("Configure credentials using one of:")
        click.echo("  - AWS CloudShell: credentials are injected from your portal session")
        click.echo("  - Local AWS CLI:  run `aws configure` or set AWS_PROFILE")
        click.echo("  - CI/CD (OIDC):   see docs/aws.md for OIDC role setup")
        click.echo("  - Environment:    set AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY")
        click.echo()
        click.echo("Run `cleancloud doctor --provider aws` to diagnose.")
        sys.exit(EXIT_PERMISSION_ERROR)

    except Exception as e:
        click.echo()
        click.echo(f"Unexpected error: {type(e).__name__}: {e}")
        click.echo()
        click.echo("This may be a bug. Please report it:")
        click.echo("  https://github.com/cleancloud-io/cleancloud/issues/new")
        if __import__("os").environ.get("CLEANCLOUD_DEBUG"):
            import traceback

            traceback.print_exc()
        sys.exit(EXIT_ERROR)
