"""DriftGuard CLI — drift detection and remediation from the command line.

Usage:
    driftguard scan --state terraform.tfstate --provider azure
    driftguard watch --state terraform.tfstate --interval 300
    driftguard fix --scan-id scan-abc123 --format terraform
    driftguard history
    driftguard report
    driftguard baseline list
    driftguard policy list
"""

from __future__ import annotations

import json
import sys
import time
from pathlib import Path
from typing import Optional

try:
    import click
    from rich.console import Console
    from rich.table import Table
    from rich.panel import Panel
    from rich import box
except ImportError:
    print("DriftGuard CLI requires 'click' and 'rich'. Install with: pip install autoai-driftguard")
    sys.exit(1)

from .core.differ import DriftDiffer, DriftResult
from .core.classifier import DriftClassifier
from .core.remediation import RemediationEngine
from .core.pr_generator import PRGenerator
from .core.baseline import BaselineManager
from .iac.terraform import TerraformStateParser
from .iac.pulumi import PulumiStateParser
from .policies.security import SecurityPolicyEngine
from .policies.cost import CostPolicyEngine
from .policies.compliance import CompliancePolicyEngine
from .store import DriftStore, DriftSeverity, DriftClassification, RemediationStatus

console = Console()


def _get_store(db: str) -> DriftStore:
    return DriftStore(db)


def _parse_state(state_path: str, iac: str) -> tuple[dict, dict]:
    """Parse IaC state file and return (resources, metadata)."""
    path = Path(state_path)
    if iac == "terraform":
        parser = TerraformStateParser()
        resources = parser.parse_file(path)
        desired = {}
        metadata = {}
        for addr, res in resources.items():
            desired[addr] = res.attributes
            metadata[addr] = {
                "type": res.type,
                "id": res.resource_id,
                "name": res.display_name,
                "provider": res.cloud_provider,
            }
        return desired, metadata
    elif iac == "pulumi":
        parser_p = PulumiStateParser()
        resources = parser_p.parse_file(path)
        desired = {}
        metadata = {}
        for urn, res in resources.items():
            merged = {**res.attributes, **res.outputs}
            desired[urn] = merged
            metadata[urn] = {
                "type": res.terraform_type,
                "id": res.resource_id,
                "name": res.display_name,
                "provider": res.cloud_provider,
            }
        return desired, metadata
    else:
        raise click.BadParameter(f"Unsupported IaC format: {iac}")


@click.group()
@click.version_option(package_name="autoai-driftguard")
def main() -> None:
    """DriftGuard -- Infrastructure Drift Detection & Self-Healing."""
    pass


@main.command()
@click.option("--state", required=True, help="Path to IaC state file (terraform.tfstate or pulumi export)")
@click.option("--iac", default="terraform", type=click.Choice(["terraform", "pulumi"]), help="IaC tool")
@click.option("--provider", default="auto", help="Cloud provider (azure/aws/gcp/auto)")
@click.option("--db", default="driftguard.db", help="SQLite database path")
@click.option("--json-output", is_flag=True, help="Output as JSON")
@click.option("--policy-check", is_flag=True, default=True, help="Run security/cost/compliance policies")
def scan(state: str, iac: str, provider: str, db: str, json_output: bool, policy_check: bool) -> None:
    """Scan for infrastructure drift."""
    store = _get_store(db)

    with console.status("[bold blue]Parsing IaC state..."):
        desired, metadata = _parse_state(state, iac)

    console.print(f"[green]Parsed {len(desired)} resources from {iac} state[/green]")

    # In a real deployment, we'd fetch live cloud state here.
    # For now, we demonstrate with the state file as both desired and actual
    # (with modifications for demo purposes)
    actual = dict(desired)  # Placeholder — replace with live cloud queries

    with console.status("[bold blue]Comparing desired vs actual state..."):
        differ = DriftDiffer(store=store)
        result = differ.compare(
            desired=desired,
            actual=actual,
            resource_metadata=metadata,
            provider=provider if provider != "auto" else "multi",
            iac_source=iac,
            state_path=state,
        )

    # Run policy checks
    violations = []
    if policy_check:
        sec_engine = SecurityPolicyEngine()
        cost_engine = CostPolicyEngine()
        comp_engine = CompliancePolicyEngine()
        for addr, attrs in desired.items():
            res_type = metadata.get(addr, {}).get("type", "unknown")
            violations.extend(sec_engine.evaluate(addr, res_type, attrs))
            violations.extend(cost_engine.evaluate(addr, res_type, attrs))
            violations.extend(comp_engine.evaluate(addr, res_type, attrs))

    if json_output:
        output = {
            "summary": result.to_summary(),
            "drifts": [
                {
                    "resource": d.resource_address,
                    "type": d.resource_type,
                    "diff_type": d.diff_type,
                    "severity": d.severity.value,
                    "category": d.category.value,
                    "changes": [
                        {"attr": a.attribute, "desired": a.desired, "actual": a.actual, "type": a.diff_type}
                        for a in d.attribute_diffs
                    ],
                }
                for d in result.resource_diffs
            ],
            "policy_violations": [v.to_dict() for v in violations],
        }
        click.echo(json.dumps(output, indent=2, default=str))
        return

    # Rich output
    _print_scan_summary(result)
    if result.resource_diffs:
        _print_drift_table(result)
    if violations:
        _print_violations(violations)


@main.command()
@click.option("--state", required=True, help="Path to IaC state file")
@click.option("--iac", default="terraform", type=click.Choice(["terraform", "pulumi"]))
@click.option("--interval", default=300, help="Scan interval in seconds")
@click.option("--db", default="driftguard.db")
def watch(state: str, iac: str, interval: int, db: str) -> None:
    """Continuously watch for drift at regular intervals."""
    console.print(f"[bold blue]Watching {state} every {interval}s. Press Ctrl+C to stop.[/bold blue]")
    store = _get_store(db)
    scan_count = 0
    try:
        while True:
            scan_count += 1
            console.print(f"\n[dim]--- Scan #{scan_count} at {time.strftime('%H:%M:%S')} ---[/dim]")
            desired, metadata = _parse_state(state, iac)
            actual = dict(desired)
            differ = DriftDiffer(store=store)
            result = differ.compare(
                desired=desired, actual=actual, resource_metadata=metadata,
                provider="auto", iac_source=iac, state_path=state,
            )
            if result.resource_diffs:
                console.print(f"[red]Drift detected: {len(result.resource_diffs)} resource(s)[/red]")
                _print_drift_table(result)
            else:
                console.print(f"[green]No drift. Score: {result.drift_score}/100[/green]")
            time.sleep(interval)
    except KeyboardInterrupt:
        console.print(f"\n[yellow]Stopped after {scan_count} scans.[/yellow]")


@main.command()
@click.option("--scan-id", help="Scan ID to generate fixes for")
@click.option("--drift-id", help="Specific drift ID to fix")
@click.option("--format", "fmt", default="terraform", type=click.Choice(["terraform", "pulumi"]))
@click.option("--db", default="driftguard.db")
@click.option("--create-pr", is_flag=True, help="Create a GitHub PR with the fix")
@click.option("--dry-run", is_flag=True, help="Generate files but don't push/PR")
def fix(scan_id: Optional[str], drift_id: Optional[str], fmt: str, db: str, create_pr: bool, dry_run: bool) -> None:
    """Generate IaC remediation code for detected drift."""
    store = _get_store(db)
    engine = RemediationEngine()

    drifts = store.get_drifts(scan_id=scan_id, limit=100)
    if not drifts:
        console.print("[yellow]No drifts found for the specified scan.[/yellow]")
        return

    console.print(f"[blue]Generating {fmt} fixes for {len(drifts)} drift(s)...[/blue]")
    # Note: In a full implementation, we'd reconstruct ResourceDiff objects from the stored records.
    # For now, print the guidance.
    console.print("[dim]Remediation code generation requires full ResourceDiff context.[/dim]")
    console.print("[dim]Use 'driftguard scan' with --json-output for structured drift data.[/dim]")


@main.command()
@click.option("--scan-id", help="Filter by scan ID")
@click.option("--severity", type=click.Choice(["critical", "high", "medium", "low", "info"]))
@click.option("--limit", default=50, help="Max results")
@click.option("--db", default="driftguard.db")
def history(scan_id: Optional[str], severity: Optional[str], limit: int, db: str) -> None:
    """View drift detection history."""
    store = _get_store(db)
    sev = DriftSeverity(severity) if severity else None
    drifts = store.get_drifts(scan_id=scan_id, severity=sev, limit=limit)
    if not drifts:
        console.print("[dim]No drift history found.[/dim]")
        return

    table = Table(title="Drift History", box=box.ROUNDED)
    table.add_column("ID", style="dim", max_width=15)
    table.add_column("Resource", style="cyan")
    table.add_column("Attribute", style="blue")
    table.add_column("Severity", justify="center")
    table.add_column("Status", justify="center")
    table.add_column("Detected", style="dim")

    severity_colors = {
        "critical": "bold red",
        "high": "red",
        "medium": "yellow",
        "low": "green",
        "info": "dim",
    }

    for d in drifts:
        sev_val = d["severity"]
        color = severity_colors.get(sev_val, "white")
        detected = time.strftime("%Y-%m-%d %H:%M", time.localtime(d["detected_at"]))
        table.add_row(
            d["id"][:15],
            d["resource_name"],
            d["attribute"],
            f"[{color}]{sev_val.upper()}[/{color}]",
            d["remediation_status"],
            detected,
        )

    console.print(table)


@main.command()
@click.option("--days", default=30, help="Report period in days")
@click.option("--db", default="driftguard.db")
@click.option("--json-output", is_flag=True)
def report(days: int, db: str, json_output: bool) -> None:
    """Generate drift posture report."""
    store = _get_store(db)
    trends = store.get_drift_trends(days)
    scans = store.get_scans(limit=10)

    if json_output:
        click.echo(json.dumps({"trends": trends, "recent_scans": scans}, indent=2, default=str))
        return

    panel = Panel(
        f"[bold]Period:[/bold] {trends['period_days']} days\n"
        f"[bold]Total drifts:[/bold] {trends['total_drifts']}\n"
        f"[bold]Resolved:[/bold] {trends['resolved_drifts']}\n"
        f"[bold]Resolution rate:[/bold] {trends['resolution_rate']}%\n\n"
        f"[bold]By severity:[/bold] {json.dumps(trends['by_severity'], indent=2)}\n"
        f"[bold]By category:[/bold] {json.dumps(trends['by_category'], indent=2)}",
        title="Drift Posture Report",
        border_style="blue",
    )
    console.print(panel)


@main.group()
def baseline() -> None:
    """Manage drift baselines."""
    pass


@baseline.command("list")
@click.option("--db", default="driftguard.db")
def baseline_list(db: str) -> None:
    """List active baselines."""
    store = _get_store(db)
    mgr = BaselineManager(store)
    baselines = mgr.get_all()

    if not baselines:
        console.print("[dim]No active baselines.[/dim]")
        return

    table = Table(title="Active Baselines", box=box.ROUNDED)
    table.add_column("Resource ID", style="cyan")
    table.add_column("Attribute", style="blue")
    table.add_column("Accepted Value")
    table.add_column("Reason")
    table.add_column("Expires", style="dim")

    for b in baselines:
        expires = time.strftime("%Y-%m-%d", time.localtime(b["expires_at"])) if b.get("expires_at") else "never"
        table.add_row(
            str(b["resource_id"])[:40],
            b["attribute"],
            str(b["accepted_value"])[:30],
            b["reason"],
            expires,
        )
    console.print(table)


@baseline.command("add")
@click.option("--resource-id", required=True)
@click.option("--attribute", required=True)
@click.option("--value", required=True)
@click.option("--reason", required=True)
@click.option("--ttl-days", type=int, help="Auto-expire after N days")
@click.option("--db", default="driftguard.db")
def baseline_add(resource_id: str, attribute: str, value: str, reason: str, ttl_days: Optional[int], db: str) -> None:
    """Add a drift baseline."""
    store = _get_store(db)
    mgr = BaselineManager(store)
    entry = mgr.accept_drift(
        resource_id=resource_id,
        resource_type="manual",
        attribute=attribute,
        accepted_value=value,
        reason=reason,
        ttl_days=ttl_days,
    )
    console.print(f"[green]Baseline added for {resource_id}/{attribute}[/green]")


@main.group()
def policy() -> None:
    """Manage drift detection policies."""
    pass


@policy.command("list")
@click.option("--category", type=click.Choice(["security", "cost", "compliance", "all"]), default="all")
def policy_list(category: str) -> None:
    """List drift detection policies."""
    engines = []
    if category in ("security", "all"):
        engines.append(("Security", SecurityPolicyEngine()))
    if category in ("cost", "all"):
        engines.append(("Cost", CostPolicyEngine()))
    if category in ("compliance", "all"):
        engines.append(("Compliance", CompliancePolicyEngine()))

    for name, engine in engines:
        rules = engine.get_enabled_rules() if hasattr(engine, "get_enabled_rules") else []
        table = Table(title=f"{name} Policies", box=box.ROUNDED)
        table.add_column("ID", style="cyan")
        table.add_column("Name", style="bold")
        table.add_column("Severity", justify="center")
        table.add_column("Description")

        severity_colors = {
            "critical": "bold red", "high": "red", "medium": "yellow",
            "low": "green", "info": "dim",
        }
        for r in rules:
            color = severity_colors.get(r["severity"], "white")
            table.add_row(
                r["id"],
                r["name"],
                f"[{color}]{r['severity'].upper()}[/{color}]",
                r["description"],
            )
        console.print(table)
        console.print()


def _print_scan_summary(result: DriftResult) -> None:
    """Print scan summary panel."""
    score_color = "green" if result.drift_score >= 90 else "yellow" if result.drift_score >= 70 else "red"
    panel = Panel(
        f"[bold]Scan ID:[/bold] {result.scan_id}\n"
        f"[bold]Provider:[/bold] {result.provider}\n"
        f"[bold]Resources:[/bold] {result.total_resources}\n"
        f"[bold]Drifted:[/bold] {result.drifted_count}\n"
        f"[bold]Deleted:[/bold] {result.deleted_count}\n"
        f"[bold]Unmanaged:[/bold] {result.unmanaged_count}\n"
        f"[bold]Drift Score:[/bold] [{score_color}]{result.drift_score}/100[/{score_color}]\n"
        f"[bold]Duration:[/bold] {result.completed_at - result.started_at:.2f}s",
        title="Scan Summary",
        border_style="blue",
    )
    console.print(panel)


def _print_drift_table(result: DriftResult) -> None:
    """Print drift results table."""
    table = Table(title="Detected Drift", box=box.ROUNDED)
    table.add_column("Resource", style="cyan")
    table.add_column("Type")
    table.add_column("Drift", justify="center")
    table.add_column("Severity", justify="center")
    table.add_column("Changes", justify="right")

    severity_colors = {
        DriftSeverity.CRITICAL: "bold red",
        DriftSeverity.HIGH: "red",
        DriftSeverity.MEDIUM: "yellow",
        DriftSeverity.LOW: "green",
        DriftSeverity.INFO: "dim",
    }

    for d in result.resource_diffs:
        color = severity_colors.get(d.severity, "white")
        table.add_row(
            d.resource_name[:40],
            d.resource_type,
            d.diff_type,
            f"[{color}]{d.severity.value.upper()}[/{color}]",
            str(len(d.attribute_diffs)),
        )
    console.print(table)


def _print_violations(violations: list) -> None:
    """Print policy violations."""
    table = Table(title="Policy Violations", box=box.ROUNDED)
    table.add_column("Rule", style="cyan")
    table.add_column("Name", style="bold")
    table.add_column("Severity", justify="center")
    table.add_column("Resource")
    table.add_column("Message")

    severity_colors = {
        "critical": "bold red", "high": "red", "medium": "yellow",
        "low": "green", "info": "dim",
    }

    for v in violations:
        sev = v.severity.value if hasattr(v.severity, "value") else str(v.severity)
        color = severity_colors.get(sev, "white")
        table.add_row(
            v.rule_id,
            v.rule_name,
            f"[{color}]{sev.upper()}[/{color}]",
            v.resource_address[:30],
            v.message[:60],
        )
    console.print(table)


if __name__ == "__main__":
    main()
