"""
Certificate Commands Module - Certificate Inventory and Lifecycle

KISS Principle: Focused on READ-ONLY certificate discovery and reporting
DRY Principle: Reuses BaseResourceCollector and common CLI patterns

Provides certificate inventory across AWS ACM, IAM server certs, and
optionally Azure Key Vault.
"""

import csv
import json
import logging
import os
import subprocess
from datetime import datetime, timezone
from pathlib import Path
from typing import Dict, List, Optional, Tuple

logger = logging.getLogger(__name__)

import click
from botocore.exceptions import ClientError
from rich.table import Table as RichTable
from rich.tree import Tree
from rich.markup import escape as rich_escape

from runbooks.common.cli_decorators import common_aws_options
from runbooks.common.error_handling import handle_cli_import_error, handle_cli_operation_error
from runbooks.common.rich_utils import console
from runbooks.inventory.models.certificate import (
    CertificateInventoryResult,
    CertificateRecord,
    CertificateSource,
    CertificateStatus,
    ExpiryBucket,
)


# Rich markup per expiry bucket
_BUCKET_STYLE = {
    ExpiryBucket.EXPIRED: "[bold red]",
    ExpiryBucket.CRITICAL_7D: "[red]",
    ExpiryBucket.WARNING_30D: "[yellow]",
    ExpiryBucket.ATTENTION_90D: "[dim yellow]",
    ExpiryBucket.VALID: "[green]",
}


def _style_bucket(bucket: ExpiryBucket, text: str) -> str:
    """Wrap text in the Rich markup for the given expiry bucket."""
    style = _BUCKET_STYLE.get(bucket, "[white]")
    return f"{style}{rich_escape(str(text))}[/]"


def _build_session(profile: Optional[str], region: str):
    """Create a boto3 session from profile and region."""
    import boto3

    return boto3.Session(profile_name=profile, region_name=region) if profile else boto3.Session(region_name=region)


def _run_scan(
    profile: Optional[str],
    region: str,
    all_accounts: bool,
    management_profile: Optional[str],
    ops_profile: Optional[str] = None,
) -> CertificateInventoryResult:
    """Execute single-account or multi-account certificate scan.

    For --all-accounts, tries Config Aggregator (P1) first via ops_profile,
    then falls back to AssumeRole (P3) via management_profile.

    CertificateCollector (which validates credentials on init) is only
    instantiated after P1 is exhausted, using the resolved profile.
    """
    from runbooks.inventory.collectors.aws_certificates import CertificateCollector

    if all_accounts:
        # P1: Try Config Aggregator org-wide (fast, no AssumeRole needed).
        # This path uses ops_profile directly — do NOT create CertificateCollector
        # yet, because profile may be None when only --ops-profile is provided.
        config_agg_profile = ops_profile or profile
        if config_agg_profile:
            try:
                from runbooks.security.cert_inventory import CertificateInventoryCollector

                ca_session = _build_session(config_agg_profile, region)
                legacy_collector = CertificateInventoryCollector()
                legacy_certs = legacy_collector.discover_aws_acm_org_wide(ca_session, region=region)
                if legacy_certs:
                    # Resolve account names for CFO/CTO-facing output (Rule 6)
                    account_ids = list(set(c.account_or_subscription for c in legacy_certs))
                    account_names = {}
                    try:
                        org_client = ca_session.client("organizations", region_name="us-east-1")
                        for acct_id in account_ids:
                            try:
                                acct = org_client.describe_account(AccountId=acct_id)
                                account_names[acct_id] = acct["Account"].get("Name", "")
                            except ClientError:
                                account_names[acct_id] = ""
                    except ClientError:
                        pass  # No org access — names stay empty
                    console.print(
                        f"[dim]Config Aggregator: {len(legacy_certs)} ACM certs "
                        f"across {len(set(c.account_or_subscription for c in legacy_certs))} accounts[/dim]"
                    )
                    result = CertificateInventoryResult()
                    for lc in legacy_certs:
                        result.certificates.append(
                            CertificateRecord(
                                certificate_arn=lc.identifier,
                                domain_name=lc.domain,
                                account_id=lc.account_or_subscription,
                                account_name=account_names.get(lc.account_or_subscription, ""),
                                region=lc.region,
                                source=CertificateSource.ACM,
                                status=CertificateStatus(lc.status)
                                if lc.status in CertificateStatus.__members__
                                else CertificateStatus.ISSUED,
                                issuer=lc.issuer,
                                key_algorithm=lc.key_algorithm,
                                certificate_type=lc.cert_type,
                                not_after=lc.not_after,
                                in_use=lc.in_use,
                                usage=[],
                                auto_renewal_eligible=lc.renewal_status == "SUCCESS",
                                tags=lc.tags,
                                discovered_at=datetime.now(timezone.utc),
                            )
                        )
                    result.build_summary()
                    return result
            except Exception as e:
                p1_error = str(e)
                logger.info(f"Config Aggregator path failed, falling back to AssumeRole: {e}")
            else:
                p1_error = "no certificates found"

        # P3: Fallback to AssumeRole per-account.
        # Resolve effective profile: prefer explicit --profile, else --ops-profile.
        if not management_profile:
            if ops_profile:
                raise click.UsageError(
                    f"Config Aggregator failed with --ops-profile {ops_profile}: {p1_error}\n"
                    f"Check: aws sso login --profile={ops_profile}\n"
                    f"Or provide --management-profile for AssumeRole fallback."
                )
            raise click.UsageError(
                "--management-profile is required with --all-accounts (or --ops-profile for Config Aggregator)"
            )
        effective_profile = profile or ops_profile
        session = _build_session(effective_profile, region)
        collector = CertificateCollector(session)
        return collector.scan_multi_account(management_profile=management_profile, region=region)
    else:
        # Single-account path: profile may be None (uses ambient credentials).
        session = _build_session(profile, region)
        collector = CertificateCollector(session)
        return collector.scan_single_account(profile=profile, region=region)


def _display_cert_table(
    certificates: List[CertificateRecord],
    title: str = "Certificate Inventory",
    show_renewal_status: bool = False,
) -> None:
    """Render a Rich table of certificates with colour-coded expiry buckets."""
    table = RichTable(
        title=title,
        show_header=True,
        header_style="bold cyan",
        show_lines=False,
    )
    table.add_column("Domain", style="cyan", no_wrap=True)
    table.add_column("Status", no_wrap=True)
    table.add_column("Expiry", no_wrap=True)
    table.add_column("Days", no_wrap=True, justify="right")
    table.add_column("Source", no_wrap=True)
    table.add_column("In Use", no_wrap=True)
    table.add_column("ManagedBy", no_wrap=True)
    table.add_column("Account", no_wrap=True)
    if show_renewal_status:
        table.add_column("Renewal", no_wrap=True)

    for cert in certificates:
        bucket = cert.expiry_bucket
        days = cert.days_until_expiry
        days_str = str(days) if days is not None else "N/A"
        expiry_str = cert.not_after.strftime("%Y-%m-%d") if cert.not_after else "N/A"

        row = [
            cert.domain_name,
            _style_bucket(bucket, cert.status.value),
            _style_bucket(bucket, expiry_str),
            _style_bucket(bucket, days_str),
            cert.source.value,
            "[green]Yes[/green]" if cert.in_use else "[dim]No[/dim]",
            cert.managed_by.value,
            cert.account_id or cert.account_name,
        ]
        if show_renewal_status:
            rs = cert.renewal_status or "N/A"
            if rs == "SUCCESS":
                row.append("[green]SUCCESS[/green]")
            elif rs == "FAILED":
                row.append("[red]FAILED[/red]")
            elif rs == "PENDING_AUTO_RENEWAL":
                row.append("[yellow]PENDING[/yellow]")
            else:
                row.append(f"[dim]{rich_escape(rs)}[/dim]")
        table.add_row(*row)

    console.print(table)


def _display_summary(result: CertificateInventoryResult) -> None:
    """Print a summary panel after the certificate table."""
    s = result.summary
    console.print(
        f"\n[bold]Summary:[/bold] {s.total_certificates} certificates | "
        f"In use: {s.in_use_count} | Unused: {s.unused_count} | "
        f"Auto-renew eligible: {s.auto_renewal_eligible_count}"
    )

    if s.by_expiry_bucket:
        parts = []
        for bucket_name in ["EXPIRED", "CRITICAL_7D", "WARNING_30D", "ATTENTION_90D", "VALID"]:
            count = s.by_expiry_bucket.get(bucket_name, 0)
            if count:
                bucket = ExpiryBucket(bucket_name)
                styled = _style_bucket(bucket, f"{bucket_name}: {count}")
                parts.append(styled)
        if parts:
            console.print("Expiry: " + " | ".join(parts))

    console.print(f"Accounts scanned: {s.accounts_scanned} | Failed: {s.accounts_failed}")


def _export_csv(certificates: List[CertificateRecord], path: str) -> None:
    """Export certificate list to CSV."""
    fieldnames = [
        "domain_name",
        "status",
        "expiry_bucket",
        "days_until_expiry",
        "not_after",
        "not_before",
        "source",
        "certificate_type",
        "in_use",
        "auto_renewal_eligible",
        "managed_by",
        "account_id",
        "account_name",
        "region",
        "issuer",
        "key_algorithm",
        "owner_team",
        "certificate_arn",
    ]
    with open(path, "w", newline="", encoding="utf-8") as fh:
        writer = csv.DictWriter(fh, fieldnames=fieldnames, extrasaction="ignore")
        writer.writeheader()
        for cert in certificates:
            writer.writerow(
                {
                    "domain_name": cert.domain_name,
                    "status": cert.status.value,
                    "expiry_bucket": cert.expiry_bucket.value,
                    "days_until_expiry": cert.days_until_expiry,
                    "not_after": cert.not_after.isoformat() if cert.not_after else "",
                    "not_before": cert.not_before.isoformat() if cert.not_before else "",
                    "source": cert.source.value,
                    "certificate_type": cert.certificate_type,
                    "in_use": cert.in_use,
                    "auto_renewal_eligible": cert.auto_renewal_eligible,
                    "managed_by": cert.managed_by.value,
                    "account_id": cert.account_id,
                    "account_name": cert.account_name,
                    "region": cert.region,
                    "issuer": cert.issuer,
                    "key_algorithm": cert.key_algorithm,
                    "owner_team": cert.owner_team,
                    "certificate_arn": cert.certificate_arn,
                }
            )
    console.print(f"[dim]CSV exported: {path}[/dim]")


def _export_json_file(result: CertificateInventoryResult, path: str) -> None:
    """Export full inventory result to JSON."""
    data = result.model_dump(mode="json")
    with open(path, "w", encoding="utf-8") as fh:
        json.dump(data, fh, indent=2, default=str)
    console.print(f"[dim]JSON exported: {path}[/dim]")


def _generate_report_markdown(result: CertificateInventoryResult, output_dir: str) -> str:
    """Generate a Markdown executive assessment report."""
    s = result.summary
    now = datetime.utcnow()
    report_path = Path(output_dir) / f"certificate-assessment-{now.strftime('%Y-%m-%d')}.md"
    report_path.parent.mkdir(parents=True, exist_ok=True)

    expired = s.by_expiry_bucket.get("EXPIRED", 0)
    critical = s.by_expiry_bucket.get("CRITICAL_7D", 0)
    warning = s.by_expiry_bucket.get("WARNING_30D", 0)
    attention = s.by_expiry_bucket.get("ATTENTION_90D", 0)
    valid = s.by_expiry_bucket.get("VALID", 0)

    # Risk level
    if expired > 0 or critical > 0:
        risk_level = "CRITICAL"
    elif warning > 0:
        risk_level = "WARNING"
    elif attention > 0:
        risk_level = "ATTENTION"
    else:
        risk_level = "HEALTHY"

    lines = [
        f"# Certificate Assessment Report",
        f"",
        f"**Generated**: {now.strftime('%Y-%m-%d %H:%M:%S')} UTC  ",
        f"**Risk Level**: {risk_level}  ",
        f"**Accounts Scanned**: {s.accounts_scanned}  ",
        f"",
        f"## Executive Summary",
        f"",
        f"| Metric | Count |",
        f"|--------|-------|",
        f"| Total Certificates | {s.total_certificates} |",
        f"| In Use | {s.in_use_count} |",
        f"| Unused | {s.unused_count} |",
        f"| Auto-Renewal Eligible | {s.auto_renewal_eligible_count} |",
        f"",
        f"## Expiry Risk Breakdown",
        f"",
        f"| Risk Bucket | Count | Action |",
        f"|-------------|-------|--------|",
        f"| EXPIRED | {expired} | Immediate renewal required |",
        f"| CRITICAL (<=7d) | {critical} | Renew today |",
        f"| WARNING (<=30d) | {warning} | Renew this week |",
        f"| ATTENTION (<=90d) | {attention} | Plan renewal |",
        f"| VALID (>90d) | {valid} | No action needed |",
        f"",
        f"## Certificates by Source",
        f"",
        f"| Source | Count |",
        f"|--------|-------|",
    ]

    for source, count in sorted(s.by_source.items()):
        lines.append(f"| {source} | {count} |")

    lines += [
        f"",
        f"## Certificates by Managed By",
        f"",
        f"| Team | Count |",
        f"|------|-------|",
    ]

    for team, count in sorted(s.by_managed_by.items()):
        lines += [f"| {team} | {count} |"]

    # Actionable items: expired + critical
    at_risk = [c for c in result.certificates if c.expiry_bucket in (ExpiryBucket.EXPIRED, ExpiryBucket.CRITICAL_7D)]
    if at_risk:
        lines += [
            f"",
            f"## Immediate Action Required",
            f"",
            f"| Domain | Status | Expiry | Account | ManagedBy |",
            f"|--------|--------|--------|---------|-----------|",
        ]
        for cert in sorted(at_risk, key=lambda c: c.not_after or datetime.min):
            expiry = cert.not_after.strftime("%Y-%m-%d") if cert.not_after else "N/A"
            lines.append(
                f"| {cert.domain_name} | {cert.status.value} | {expiry} | {cert.account_id} | {cert.managed_by.value} |"
            )

    report_path.write_text("\n".join(lines), encoding="utf-8")
    return str(report_path)


def _check_dns_cname(name: str) -> str:
    """Run `dig CNAME <name> +short` and return the resolved value (stripped) or empty string."""
    try:
        result = subprocess.run(
            ["dig", "CNAME", name, "+short"],
            capture_output=True,
            text=True,
            timeout=10,
        )
        return result.stdout.strip().rstrip(".")
    except (FileNotFoundError, subprocess.TimeoutExpired, OSError):
        return ""


def _get_validation_records(session, arn: str, region: str) -> List[Dict]:
    """Call describe_certificate and extract DomainValidationOptions with ResourceRecord entries."""
    try:
        acm = session.client("acm", region_name=region)
        detail = acm.describe_certificate(CertificateArn=arn).get("Certificate", {})
        options = detail.get("DomainValidationOptions", [])
        records = []
        for opt in options:
            rr = opt.get("ResourceRecord", {})
            if rr.get("Type") == "CNAME" and rr.get("Name"):
                records.append(
                    {
                        "domain": opt.get("DomainName", ""),
                        "cname_name": rr["Name"].rstrip("."),
                        "cname_value": rr.get("Value", "").rstrip("."),
                        "validation_status": opt.get("ValidationStatus", ""),
                    }
                )
        return records
    except Exception as e:
        logger.debug(f"describe_certificate failed for {arn}: {e}")
        return []


def create_cert_group():
    """
    Create the cert command group with all subcommands.

    Returns:
        Click Group object with all certificate commands
    """

    class RichCertGroup(click.Group):
        """Custom Click Group with Rich Tree/Table help display."""

        def format_help(self, ctx, formatter):
            """Format help text with Rich Tree/Table categorization."""
            test_mode = os.environ.get("RUNBOOKS_TEST_MODE", "0") == "1"

            if test_mode:
                click.echo("Usage: runbooks cert [OPTIONS] COMMAND [ARGS]...")
                click.echo("")
                click.echo("  Certificate inventory and lifecycle management.")
                click.echo("")
                click.echo("Commands:")
                click.echo("  inventory   Discover certificates across AWS accounts and Azure")
                click.echo("  expiring    Show certificates expiring within N days")
                click.echo("  report      Generate executive certificate assessment report")
                click.echo("  triage      Combined triage: inventory + expiring + executive report")
                click.echo("  dns-check   Check ACM DNS validation CNAME records via dig")
                return

            categories = {
                "Discovery": [
                    ("inventory", "Discover certificates across AWS accounts and Azure subscriptions"),
                    ("expiring", "Show certificates expiring within N days (default: 30)"),
                    ("dns-check", "Check ACM DNS validation CNAME records via dig"),
                ],
                "Reporting": [
                    ("report", "Generate executive certificate assessment report (Markdown)"),
                    ("triage", "Combined triage: inventory + expiring + executive report"),
                ],
            }

            max_cmd_len = max(len(cmd) for cat in categories.values() for cmd, _ in cat)
            cmd_width = max_cmd_len + 2

            tree = Tree("[bold cyan]Certificate Commands[/bold cyan] (5 commands)")
            for category_name, commands in categories.items():
                branch = tree.add(f"[bold green]{category_name}[/bold green] [dim]({len(commands)} commands)[/dim]")
                table = RichTable(show_header=True, box=None, padding=(0, 2))
                table.add_column("Command", style="cyan", no_wrap=True, min_width=cmd_width, max_width=cmd_width)
                table.add_column("Description", style="dim", no_wrap=False, overflow="fold")
                for cmd, desc in commands:
                    table.add_row(cmd, desc)
                branch.add(table)

            console.print(tree)
            console.print("\n[blue]Usage: runbooks cert [COMMAND] [OPTIONS][/blue]")
            console.print("[blue]READ-ONLY: no certificate modifications[/blue]")

    @click.group(cls=RichCertGroup, invoke_without_command=True)
    @common_aws_options
    @click.pass_context
    def cert(ctx, profile, region, dry_run):
        """
        Certificate inventory and lifecycle management.

        Discovers certificates across AWS ACM, IAM server certificates, and
        optionally Azure Key Vault. All operations are READ-ONLY.

        Examples:
            runbooks cert inventory
            runbooks cert inventory --all-accounts --management-profile mgmt
            runbooks cert expiring --days 30
            runbooks cert report --output-dir ./cert-reports
        """
        ctx.obj.update({"profile": profile, "region": region, "dry_run": dry_run})
        if ctx.invoked_subcommand is None:
            click.echo(ctx.get_help())

    @cert.command()
    @click.option("--profile", default=None, help="AWS profile name")
    @click.option("--region", default="ap-southeast-2", help="AWS region")
    @click.option(
        "--all-accounts",
        is_flag=True,
        help="Scan all Landing Zone accounts via Organizations",
    )
    @click.option(
        "--management-profile",
        default=None,
        help="AWS management account profile for Organizations (required with --all-accounts)",
    )
    @click.option(
        "--ops-profile",
        default=None,
        envvar="AWS_OPERATIONS_PROFILE",
        help="AWS profile with Config Aggregator access (P1 org-wide path, fastest). Falls back to AWS_OPERATIONS_PROFILE env var.",
    )
    @click.option("--azure", is_flag=True, help="Include Azure Key Vault certificates")
    @click.option("--subscription", default=None, help="Azure subscription ID (required with --azure)")
    @click.option("--export-csv", default=None, type=click.Path(), help="Export to CSV file")
    @click.option("--export-json", default=None, type=click.Path(), help="Export to JSON file")
    @click.option("--output-dir", default=None, type=click.Path(), help="Output directory for reports")
    @click.option("--count-only", is_flag=True, default=False, help="Print certificate count only (no detail table)")
    @click.option(
        "--status", default=None, help="Filter by ACM certificate status (e.g., ISSUED, EXPIRED, PENDING_VALIDATION)"
    )
    @click.option("--in-use-only", is_flag=True, default=False, help="Show only in-use certificates (skip unused)")
    @click.option(
        "--renewal-status",
        "show_renewal_status",
        is_flag=True,
        default=False,
        help="Include ACM renewal status column (SUCCESS, PENDING_AUTO_RENEWAL, FAILED). Already collected — no extra API calls.",
    )
    def inventory(
        profile,
        region,
        all_accounts,
        management_profile,
        ops_profile,
        azure,
        subscription,
        export_csv,
        export_json,
        output_dir,
        count_only,
        status,
        in_use_only,
        show_renewal_status,
    ):
        """
        Discover certificates across AWS accounts and Azure subscriptions.

        Scans ACM (all regions), IAM server certificates (global), and
        optionally Azure Key Vault. Results are displayed in a colour-coded
        table and can be exported to CSV / JSON.

        Examples:
            runbooks cert inventory
            runbooks cert inventory --all-accounts --management-profile mgmt-profile
            runbooks cert inventory --azure --subscription 14ec7245-xxxx
            runbooks cert inventory --export-csv certs.csv --export-json certs.json
            runbooks cert inventory --renewal-status
        """
        try:
            console.print("[bold cyan]Certificate Inventory[/bold cyan]")
            console.print(f"[dim]Profile: {profile or 'default'} | Region: {region}[/dim]")
            console.print(f"[dim]Mode: {'Multi-account' if all_accounts else 'Single account'}[/dim]\n")

            result = _run_scan(profile, region, all_accounts, management_profile, ops_profile=ops_profile)

            if azure:
                if not subscription:
                    raise click.UsageError("--subscription is required with --azure")
                try:
                    from runbooks.inventory.collectors.aws_certificates import CertificateCollector

                    session = _build_session(profile, region)
                    collector = CertificateCollector(session)
                    azure_certs = collector.scan_azure_keyvault(subscription_id=subscription)
                    result.certificates.extend(azure_certs)
                    result.build_summary()
                    console.print(f"[dim]Azure Key Vault: {len(azure_certs)} certificates[/dim]")
                except (OSError, ValueError, RuntimeError) as e:
                    console.print(f"[yellow]Azure Key Vault scan failed (continuing): {rich_escape(str(e))}[/yellow]")

            if status:
                result.certificates = [c for c in result.certificates if c.status.value.upper() == status.upper()]
                result.build_summary()

            if in_use_only:
                result.certificates = [c for c in result.certificates if c.in_use]
                result.build_summary()

            if not result.certificates:
                console.print("[yellow]No certificates found.[/yellow]")
                return result

            if count_only:
                click.echo(len(result.certificates))
            else:
                _display_cert_table(
                    result.certificates, title="Certificate Inventory", show_renewal_status=show_renewal_status
                )
                _display_summary(result)

            if export_csv:
                _export_csv(result.certificates, export_csv)
            if export_json:
                _export_json_file(result, export_json)
            if output_dir:
                Path(output_dir).mkdir(parents=True, exist_ok=True)
                csv_path = str(Path(output_dir) / f"certificates-{datetime.utcnow().strftime('%Y-%m-%d')}.csv")
                json_path = str(Path(output_dir) / f"certificates-{datetime.utcnow().strftime('%Y-%m-%d')}.json")
                _export_csv(result.certificates, csv_path)
                _export_json_file(result, json_path)

            return result

        except ImportError as e:
            handle_cli_import_error("Certificate inventory module", e)
        except Exception as e:
            handle_cli_operation_error("Certificate inventory", e)

    @cert.command()
    @click.option("--days", default=30, show_default=True, help="Show certs expiring within N days")
    @click.option("--profile", default=None, help="AWS profile name")
    @click.option("--region", default="ap-southeast-2", help="AWS region")
    @click.option("--all-accounts", is_flag=True, help="Scan all Landing Zone accounts via Organizations")
    @click.option("--management-profile", default=None, help="AWS management account profile for Organizations")
    @click.option(
        "--ops-profile",
        default=None,
        envvar="AWS_OPERATIONS_PROFILE",
        help="AWS profile with Config Aggregator access (P1 org-wide path)",
    )
    @click.option("--export-csv", default=None, type=click.Path(), help="Export expiring certificates to CSV file")
    @click.option("--export-json", default=None, type=click.Path(), help="Export expiring certificates to JSON file")
    def expiring(days, profile, region, all_accounts, management_profile, ops_profile, export_csv, export_json):
        """
        Show certificates expiring within N days.

        Filters the inventory to certificates in the EXPIRED, CRITICAL_7D,
        WARNING_30D, or ATTENTION_90D buckets based on the --days threshold.

        Examples:
            runbooks cert expiring
            runbooks cert expiring --days 7
            runbooks cert expiring --days 90 --all-accounts --management-profile mgmt
            runbooks cert expiring --days 30 --export-csv expiring.csv --export-json expiring.json
        """
        try:
            console.print(f"[bold cyan]Expiring Certificates (within {days} days)[/bold cyan]")
            console.print(f"[dim]Profile: {profile or 'default'} | Region: {region}[/dim]\n")

            result = _run_scan(profile, region, all_accounts, management_profile, ops_profile=ops_profile)

            # Filter to certificates expiring within `days`
            filtered = [
                cert
                for cert in result.certificates
                if cert.days_until_expiry is not None and cert.days_until_expiry <= days
            ]

            if not filtered:
                console.print(f"[green]No certificates expiring within {days} days.[/green]")
                return []

            _display_cert_table(
                filtered,
                title=f"Certificates Expiring Within {days} Days ({len(filtered)} found)",
            )

            # Mini summary
            expired_count = sum(1 for c in filtered if c.expiry_bucket == ExpiryBucket.EXPIRED)
            critical_count = sum(1 for c in filtered if c.expiry_bucket == ExpiryBucket.CRITICAL_7D)
            if expired_count:
                console.print(
                    f"[bold red]{expired_count} certificate(s) already EXPIRED — immediate action required[/bold red]"
                )
            if critical_count:
                console.print(f"[red]{critical_count} certificate(s) CRITICAL (<=7 days) — renew today[/red]")

            if export_csv:
                _export_csv(filtered, export_csv)
            if export_json:
                filtered_result = CertificateInventoryResult(certificates=filtered)
                filtered_result.build_summary()
                _export_json_file(filtered_result, export_json)

            return filtered

        except ImportError as e:
            handle_cli_import_error("Certificate inventory module", e)
        except Exception as e:
            handle_cli_operation_error("Certificate expiry check", e)

    @cert.command()
    @click.option("--profile", default=None, help="AWS profile name")
    @click.option("--region", default="ap-southeast-2", help="AWS region")
    @click.option("--all-accounts", is_flag=True, help="Scan all Landing Zone accounts via Organizations")
    @click.option("--management-profile", default=None, help="AWS management account profile for Organizations")
    @click.option(
        "--ops-profile",
        default=None,
        envvar="AWS_OPERATIONS_PROFILE",
        help="AWS profile with Config Aggregator access (P1 org-wide path)",
    )
    @click.option(
        "--output-dir",
        default=".",
        show_default=True,
        type=click.Path(),
        help="Output directory for the Markdown report",
    )
    def report(profile, region, all_accounts, management_profile, ops_profile, output_dir):
        """
        Generate executive certificate assessment report (Markdown).

        Scans certificates, builds an expiry risk breakdown, and writes a
        Markdown file suitable for CxO or security team review.

        Examples:
            runbooks cert report
            runbooks cert report --output-dir ./cert-reports
            runbooks cert report --all-accounts --management-profile mgmt --output-dir /tmp/certs
        """
        try:
            console.print("[bold cyan]Certificate Assessment Report[/bold cyan]")
            console.print(f"[dim]Profile: {profile or 'default'} | Region: {region}[/dim]\n")

            result = _run_scan(profile, region, all_accounts, management_profile, ops_profile=ops_profile)

            if not result.certificates:
                console.print("[yellow]No certificates found — report not generated.[/yellow]")
                return result

            report_file = _generate_report_markdown(result, output_dir)
            console.print(f"[green]Report generated: {report_file}[/green]")

            _display_summary(result)
            return result

        except ImportError as e:
            handle_cli_import_error("Certificate report module", e)
        except Exception as e:
            handle_cli_operation_error("Certificate report generation", e)

    @cert.command()
    @click.option("--days", default=90, show_default=True, help="Triage window in days")
    @click.option("--profile", default=None, help="AWS profile name")
    @click.option("--region", default="ap-southeast-2", help="AWS region")
    @click.option("--all-accounts", is_flag=True, help="Scan all Landing Zone accounts")
    @click.option("--management-profile", default=None, help="AWS management account profile")
    @click.option(
        "--ops-profile",
        default=None,
        envvar="AWS_OPERATIONS_PROFILE",
        help="AWS profile with Config Aggregator access",
    )
    @click.option("--output-dir", default=".", type=click.Path(), help="Output directory for triage report")
    @click.option("--export-csv", default=None, type=click.Path(), help="Export triage results to CSV file")
    @click.option("--export-json", default=None, type=click.Path(), help="Export triage results to JSON file")
    @click.option("--in-use-only", is_flag=True, default=False, help="Show only in-use certificates (skip unused)")
    @click.option(
        "--mode",
        type=click.Choice(["cloudops", "executive", "cto", "sre"], case_sensitive=False),
        default="cloudops",
        show_default=True,
        help="Output persona mode",
    )
    @click.option("--email", "generate_email", is_flag=True, default=False, help="Generate stakeholder email template")
    def triage(
        days,
        profile,
        region,
        all_accounts,
        management_profile,
        ops_profile,
        output_dir,
        export_csv,
        export_json,
        in_use_only,
        mode,
        generate_email,
    ):
        """Combined certificate triage: inventory + expiring + executive report.

        Scans all certificates, highlights expired and expiring, generates
        an executive report with actionable triage buckets.

        Examples:
            runbooks cert triage
            runbooks cert triage --mode executive --all-accounts --ops-profile $AWS_OPERATIONS_PROFILE
            runbooks cert triage --mode sre --in-use-only
            runbooks cert triage --email --output-dir tmp/runbooks/security/
        """
        try:
            console.print("[bold cyan]Certificate Triage[/bold cyan]")
            console.print(f"[dim]Profile: {profile or 'default'} | Region: {region} | Window: {days} days[/dim]\n")

            # Phase 1: Full scan
            result = _run_scan(profile, region, all_accounts, management_profile, ops_profile=ops_profile)

            if not result.certificates:
                console.print("[yellow]No certificates found.[/yellow]")
                return result

            # Phase 1b: Apply in-use-only filter
            if in_use_only:
                result.certificates = [c for c in result.certificates if c.in_use]
                result.build_summary()

            # Phase 2: Triage buckets
            expired_in_use = [c for c in result.certificates if c.expiry_bucket == ExpiryBucket.EXPIRED and c.in_use]
            expired_unused = [
                c for c in result.certificates if c.expiry_bucket == ExpiryBucket.EXPIRED and not c.in_use
            ]
            expiring = [
                c for c in result.certificates if c.days_until_expiry is not None and 0 < c.days_until_expiry <= days
            ]
            valid = [c for c in result.certificates if c.days_until_expiry is not None and c.days_until_expiry > days]

            # Phase 3: Display using persona formatter
            from runbooks.security.cert_persona_formatter import CertPersonaFormatter

            formatter = CertPersonaFormatter(persona=mode)
            if mode == "executive":
                formatter.format_executive_summary(result, console)
            elif mode == "cto":
                formatter.format_cto_report(result, console)
            elif mode == "sre":
                formatter.format_sre_alerts(result, console)
            else:
                formatter.format_cloudops_triage(result, console)

            # Phase 4: Email template (optional)
            if generate_email:
                import time as _time

                email_text = CertPersonaFormatter.generate_stakeholder_email(result, profile=profile or "default")
                email_path = Path(output_dir) / f"cert-email-{_time.strftime('%Y-%m-%d')}.txt"
                email_path.parent.mkdir(parents=True, exist_ok=True)
                email_path.write_text(email_text)
                console.print(f"\n[green]Email template: {email_path}[/green]")

            # Phase 5: Export (before report generation)
            if export_csv:
                _export_csv(result.certificates, export_csv)
            if export_json:
                _export_json_file(result, export_json)

            # Phase 6: Generate report
            Path(output_dir).mkdir(parents=True, exist_ok=True)
            report_file = _generate_report_markdown(result, output_dir)
            console.print(f"\n[green]Report: {report_file}[/green]")

            return result

        except ImportError as e:
            handle_cli_import_error("Certificate triage module", e)
        except Exception as e:
            handle_cli_operation_error("Certificate triage", e)

    @cert.command("dns-check")
    @click.option("--profile", default=None, help="AWS profile name")
    @click.option("--region", default="ap-southeast-2", show_default=True, help="AWS region")
    @click.option("--all-accounts", is_flag=True, help="Scan all Landing Zone accounts via Organizations")
    @click.option(
        "--management-profile",
        default=None,
        help="AWS management account profile for Organizations (required with --all-accounts)",
    )
    @click.option(
        "--ops-profile",
        default=None,
        envvar="AWS_OPERATIONS_PROFILE",
        help="AWS profile with Config Aggregator access (P1 org-wide path). Falls back to AWS_OPERATIONS_PROFILE.",
    )
    def dns_check(profile, region, all_accounts, management_profile, ops_profile):
        """Check ACM DNS validation CNAME records via dig.

        Discovers certificates in PENDING_VALIDATION status or with FAILED renewal,
        then queries each certificate's required CNAME validation record and checks
        whether it is present in DNS via `dig`.

        Output table columns:
          Domain       — certificate domain name
          Required CNAME — validation record name → value
          DNS Status   — PRESENT or MISSING
          Action       — remediation step

        Examples:
            runbooks cert dns-check
            runbooks cert dns-check --ops-profile $AWS_OPERATIONS_PROFILE
            runbooks cert dns-check --all-accounts --management-profile mgmt
        """
        try:
            console.print("[bold cyan]Certificate DNS Validation Check[/bold cyan]")
            console.print(f"[dim]Profile: {profile or 'default'} | Region: {region}[/dim]\n")

            result = _run_scan(profile, region, all_accounts, management_profile, ops_profile=ops_profile)

            # Filter to certs that need DNS validation attention
            candidates = [
                c
                for c in result.certificates
                if c.status == CertificateStatus.PENDING_VALIDATION or c.renewal_status == "FAILED"
            ]

            if not candidates:
                console.print(
                    "[green]No certificates require DNS validation — all issued or no FAILED renewals.[/green]"
                )
                return []

            console.print(f"[dim]Checking DNS CNAME records for {len(candidates)} certificate(s)...[/dim]\n")

            # Build a session for describe_certificate calls (regional ACM, not aggregator)
            session = _build_session(profile or ops_profile, region)

            table = RichTable(
                title="DNS Validation Status",
                show_header=True,
                header_style="bold cyan",
                show_lines=False,
            )
            table.add_column("Domain", style="cyan", no_wrap=True)
            table.add_column("Required CNAME (name → value)", no_wrap=False, overflow="fold")
            table.add_column("DNS Status", no_wrap=True)
            table.add_column("Action", no_wrap=False, overflow="fold")

            present_count = 0
            missing_count = 0
            rows_added = 0

            for cert in candidates:
                cert_region = cert.region if cert.region and cert.region not in ("global", "azure") else region
                validation_records = _get_validation_records(session, cert.certificate_arn, cert_region)

                if not validation_records:
                    # No CNAME validation records available (e.g. EMAIL validation or IAM cert)
                    table.add_row(
                        cert.domain_name,
                        "[dim]No CNAME record[/dim]",
                        "[dim]N/A[/dim]",
                        "Check validation method in ACM console",
                    )
                    rows_added += 1
                    continue

                for vr in validation_records:
                    actual = _check_dns_cname(vr["cname_name"])
                    expected = vr["cname_value"]
                    cname_display = f"{vr['cname_name']} →\n{expected}"

                    if actual and actual == expected:
                        dns_status = "[green]PRESENT[/green]"
                        action = "DNS record verified — ACM will issue soon"
                        present_count += 1
                    elif actual:
                        dns_status = "[yellow]MISMATCH[/yellow]"
                        action = f"Expected: {expected}\nActual:   {actual}"
                        missing_count += 1
                    else:
                        dns_status = "[red]MISSING[/red]"
                        action = f"Add CNAME in DNS provider:\n{vr['cname_name']} → {expected}"
                        missing_count += 1

                    table.add_row(cert.domain_name, cname_display, dns_status, action)
                    rows_added += 1

            if rows_added:
                console.print(table)

            total = present_count + missing_count
            console.print(
                f"\n[bold]DNS Summary:[/bold] {present_count} of {total} validation record(s) present"
                + (f" | [red]{missing_count} missing[/red]" if missing_count else "")
            )

            return candidates

        except ImportError as e:
            handle_cli_import_error("Certificate DNS check module", e)
        except Exception as e:
            handle_cli_operation_error("Certificate DNS check", e)

    return cert
