"""ComplyFormError base and error classes.

Every user-facing error is a ComplyFormError subclass with title, message,
fix, and optional hint. The display() method renders a Rich Panel to stderr.
"""

from __future__ import annotations

from typing import TYPE_CHECKING

from rich.panel import Panel

if TYPE_CHECKING:
    from rich.console import Console


class ComplyFormError(Exception):
    """Base error for all ComplyForm user-facing errors."""

    def __init__(
        self,
        title: str,
        message: str,
        fix: str,
        *,
        error_code: int = 0,
        hint: str = "",
    ) -> None:
        self.title = title
        self.message = message
        self.fix = fix
        self.error_code = error_code
        self.hint = hint
        super().__init__(message)

    def display(self, console: Console, *, style: str = "red") -> None:
        """Render a Rich Panel to stderr."""
        body = self.message
        body += f"\n\nFix: {self.fix}"
        if self.hint:
            body += f"\n{self.hint}"
        console.print(Panel(body, title=f"✗ {self.title}", style=style))


# --- Error #1 ---


class MutualExclusivityError(ComplyFormError):
    """Mutually exclusive CLI flags were combined."""

    def __init__(self, message: str, fix: str) -> None:
        super().__init__(
            title="Conflicting Options",
            message=message,
            fix=fix,
            error_code=1,
        )


class CloudCredentialError(ComplyFormError):
    """Cloud provider credentials are missing or expired."""

    _FIX_MAP: dict[str, str] = {
        "gcp": "gcloud auth application-default login",
        "aws": "aws configure",
        "azure": "az login",
    }

    def __init__(self, cloud: str) -> None:
        fix = self._FIX_MAP.get(cloud, f"Configure credentials for {cloud}")
        super().__init__(
            title="Cloud Credential Error",
            message=f"Missing or expired credentials for {cloud}.",
            fix=fix,
            error_code=11,
        )


# --- Error #2 ---


class StateFileNotFoundError(ComplyFormError):
    """Terraform/OpenTofu state file not found."""

    def __init__(self, path: str) -> None:
        super().__init__(
            title="State File Not Found",
            message=f"Could not find state file at: {path}",
            fix=f"Verify the path exists: ls {path}",
            error_code=2,
            hint=(
                "For remote state, use gs://, s3://, or azure:// prefix (Pro+ tier)."
            ),
        )


# --- Error #3 ---


class UnsupportedStateFormatError(ComplyFormError):
    """State file format version is unsupported."""

    def __init__(self, version: str) -> None:
        super().__init__(
            title="Unsupported State Format",
            message=(f"State format version {version} is not supported."),
            fix=("Run tofu init -upgrade or terraform init -upgrade to upgrade."),
            error_code=3,
        )


# --- Error #4 ---


class CheckovNotFoundError(ComplyFormError):
    """Checkov binary is not installed or not on PATH."""

    def __init__(self) -> None:
        super().__init__(
            title="Checkov Not Found",
            message=("Checkov is not installed or not found on PATH."),
            fix="uv tool install --force complyform",
            error_code=4,
        )


# --- Error #5 ---


class NetworkUnreachableError(ComplyFormError):
    """Cannot reach the ComplyForm server."""

    def __init__(self, grace_days: int) -> None:
        if grace_days > 0:
            status = f"Grace period: {grace_days} day(s) remaining."
        else:
            status = "Grace period expired."
        super().__init__(
            title="Network Unreachable",
            message=(f"Cannot reach the ComplyForm server. {status}"),
            fix="Check your network connection and try again.",
            error_code=5,
            hint="Server: https://api.complyform.dev",
        )


# --- Error #6 ---


class LicenseInvalidError(ComplyFormError):
    """License key is invalid or expired."""

    def __init__(self, reason: str) -> None:
        super().__init__(
            title="License Invalid",
            message=f"License validation failed: {reason}",
            fix="complyform config activate",
            error_code=6,
        )


# --- Error #7 ---


class RemediationTierError(ComplyFormError):
    """Remediation requires a higher license tier."""

    def __init__(self, framework: str, tier: str) -> None:
        super().__init__(
            title="Remediation Tier Required",
            message=(f"Remediation for {framework} requires the {tier} tier."),
            fix="Upgrade at https://complyform.dev/pricing",
            error_code=7,
            hint="Assess is always free.",
        )


# --- Error #8 ---


class NoFindingsToRemediate(ComplyFormError):
    """No compliance findings to remediate — everything is clean."""

    def __init__(self, frameworks: list[str]) -> None:
        fw_list = ", ".join(frameworks)
        super().__init__(
            title="No Findings",
            message=(f"All checks passed for: {fw_list}. Nothing to remediate."),
            fix="No action needed.",
            error_code=8,
        )

    def display(self, console: Console, *, style: str = "green") -> None:
        """Render as a green success panel."""
        body = self.message
        body += f"\n\nFix: {self.fix}"
        if self.hint:
            body += f"\n{self.hint}"
        console.print(Panel(body, title=f"✓ {self.title}", style=style))


# --- Error #9 ---


class ProfileBundleError(ComplyFormError):
    """Profile bundle is invalid or missing."""

    def __init__(self, detail: str) -> None:
        super().__init__(
            title="Profile Bundle Error",
            message=f"Invalid profile bundle: {detail}",
            fix="complyform config activate <key>",
            error_code=9,
        )


# --- Error #10 ---


class GCPAPINotEnabledError(ComplyFormError):
    """Required GCP API is not enabled."""

    def __init__(self, api: str, project: str) -> None:
        super().__init__(
            title="GCP API Not Enabled",
            message=(f"The API '{api}' is not enabled for project '{project}'."),
            fix=(f"gcloud services enable {api} --project={project}"),
            error_code=10,
        )


# --- Error #47 ---


class ProfileBundleVersionError(ComplyFormError):
    """Profile bundle requires a newer CLI version."""

    def __init__(
        self,
        bundle_version: str,
        min_cli_version: str,
        current_cli_version: str,
    ) -> None:
        super().__init__(
            title="Profile Bundle Version Mismatch",
            message=(
                f"Your profile bundle (v{bundle_version}) requires"
                f" CLI v{min_cli_version}+."
                f" You're running v{current_cli_version}."
            ),
            fix="Upgrade: uv tool install --upgrade complyform",
            error_code=47,
        )


# --- Error #48 ---


class InvalidCloudError(ComplyFormError):
    """Unknown cloud provider specified."""

    def __init__(self, cloud: str) -> None:
        super().__init__(
            title="Invalid Cloud Provider",
            message=f"Unknown cloud provider: '{cloud}'.",
            fix="Use one of: gcp, aws, azure.",
            error_code=48,
        )


# --- Error #50 ---


class UnknownFrameworkError(ComplyFormError):
    """One or more requested framework IDs are not recognized."""

    def __init__(self, framework_ids: list[str], valid_ids: list[str]) -> None:
        ids_str = ", ".join(framework_ids)
        super().__init__(
            title="Unknown Framework",
            message=f"Unknown framework(s): {ids_str}.",
            fix="Run `complyform frameworks list` to see available frameworks.",
            error_code=50,
            hint=f"Valid IDs include: {', '.join(valid_ids[:10])}...",
        )


# --- Error #51 ---


class UnknownConfigKeyError(ComplyFormError):
    """Unknown configuration key specified."""

    def __init__(self, key: str) -> None:
        super().__init__(
            title="Unknown Config Key",
            message=f"Unknown configuration key: '{key}'.",
            fix="Run `complyform config status` to see valid keys.",
            error_code=51,
        )


# --- Error #81 ---


class IaCBinaryNotFoundError(ComplyFormError):
    """Neither tofu nor terraform found on PATH."""

    def __init__(self) -> None:
        super().__init__(
            title="IaC Binary Not Found",
            message="No IaC binary found on PATH.",
            fix=(
                "Install OpenTofu: https://opentofu.org/docs/install"
                " — or Terraform: https://terraform.io/downloads."
                " Override: set COMPLYFORM_IAC_BINARY=/path/to/binary"
            ),
            error_code=81,
        )


# --- Error #17 ---


class NoCachedAssessmentError(ComplyFormError):
    """No cached scan results for assess to consume."""

    def __init__(self, label: str) -> None:
        super().__init__(
            title="No Cached Scan",
            message=f"No cached scan found{f' for {label}' if label else ''}.",
            fix="Run: complyform scan --state=./terraform.tfstate",
            error_code=17,
        )


# --- Error #90 ---


class AttestationKeyNotFoundError(ComplyFormError):
    """Signing key missing from profile bundle."""

    def __init__(self) -> None:
        super().__init__(
            title="Attestation Key Not Found",
            message="Profile bundle missing signing key.",
            fix="Re-activate: complyform config activate <key>",
            error_code=90,
        )


# --- Error #91 ---


class AttestationVerificationFailedError(ComplyFormError):
    """Attestation verification failed."""

    def __init__(self, reason: str) -> None:
        super().__init__(
            title="Attestation Verification Failed",
            message=f"Attestation verification failed: {reason}",
            fix="Re-run assessment and generate a new attestation.",
            error_code=91,
        )


# --- Error #52 ---


def emit_provider_version_warning(
    console: Console,
    *,
    provider: str,
    detected_version: str,
    min_version: str,
    cloud: str,
) -> None:
    """Emit a provider version warning panel to stderr."""
    body = (
        f"Your {provider} provider version ({detected_version})"
        f" is older than the tested minimum ({min_version})"
        f" for {cloud} remediation patches."
        "\nPatches may require adjustment."
    )
    console.print(
        Panel(
            body,
            title="⚠ Provider Version Warning",
            style="yellow",
        )
    )


# --- Error #74 ---


class InteractiveNotTTYError(ComplyFormError):
    """Interactive mode requires a terminal."""

    def __init__(self) -> None:
        super().__init__(
            title="Interactive Mode Unavailable",
            message="Interactive mode requires a terminal.",
            fix="Use `--dry-run` for non-interactive preview.",
            error_code=74,
        )


# --- Error #75 ---


class InteractivePRConflictError(ComplyFormError):
    """Cannot combine --interactive with --open-pr or --format=json."""

    def __init__(self, detail: str | None = None) -> None:
        msg = detail or (
            "Cannot combine --interactive with --open-pr."
            " Review interactively first, then run with --open-pr."
        )
        super().__init__(
            title="Flag Conflict",
            message=msg,
            fix="Remove one of the conflicting flags.",
            error_code=75,
        )


# --- Error #12 ---


class PDFRequiresAPIError(ComplyFormError):
    """PDF generation requires API key or higher tier."""

    def __init__(self, reason: str) -> None:
        super().__init__(
            title="PDF Requires API Key",
            message=reason,
            fix="Upgrade at https://complyform.dev/pricing",
            error_code=12,
        )


# --- Error #15 ---


class BatchSizeLimitError(ComplyFormError):
    """Batch operation exceeds tier limit."""

    def __init__(self, count: int, limit: int) -> None:
        super().__init__(
            title="Batch Size Limit Exceeded",
            message=(
                f"Batch contains {count} projects (limit: {limit})."
                " Split into smaller batches."
            ),
            fix="Upgrade at https://complyform.dev/pricing",
            error_code=15,
        )


# --- Error #21 ---


class BatchOperationTierError(ComplyFormError):
    """Batch report/export/push requires Agency tier."""

    def __init__(self) -> None:
        super().__init__(
            title="Batch Operation Tier Required",
            message=(
                "Batch report/export/push requires Agency tier."
                " Batch scan and assess are available at Pro+"
                " — see error #60."
            ),
            fix="Upgrade at https://complyform.dev/pricing",
            error_code=21,
        )


# --- Error #23 ---


class ScanSourceTierError(ComplyFormError):
    """Scan source not available on current tier."""

    def __init__(self, source: str, tier: str) -> None:
        if source == "cloud_api":
            msg = "Live cloud API scanning requires Team tier."
        else:
            msg = "Remote state backends require Pro tier."
        super().__init__(
            title="Scan Source Tier Required",
            message=f"{msg} Your tier: {tier}.",
            fix="Upgrade at https://complyform.dev/pricing",
            error_code=23,
        )


# --- Error #24 ---


class FeatureNotAvailableError(ComplyFormError):
    """Feature not available on current tier."""

    def __init__(self, feature: str, tier: str) -> None:
        super().__init__(
            title="Feature Not Available",
            message=(f"Feature '{feature}' is not available on {tier} tier."),
            fix="Upgrade at https://complyform.dev/pricing",
            error_code=24,
        )


# --- Error #30 ---


class FrameworkTierError(ComplyFormError):
    """Feature requires a higher tier."""

    def __init__(self, feature: str, tier: str) -> None:
        super().__init__(
            title="Framework Tier Required",
            message=(f"Feature '{feature}' requires a higher tier. Your tier: {tier}."),
            fix="Upgrade at https://complyform.dev/pricing",
            error_code=30,
        )


# --- Error #54 ---


class MultiStateRemediateTierError(ComplyFormError):
    """Multi-state remediation requires Pro tier."""

    def __init__(self, state_count: int, tier: str) -> None:
        super().__init__(
            title="Multi-State Remediation Requires Pro Tier",
            message=(
                f"You have {state_count} state files."
                f" Multi-state remediation requires Pro tier"
                f" (current: {tier})."
                " Assessment across multiple states is free at all tiers."
            ),
            fix="Upgrade at complyform.dev/pricing",
            error_code=54,
        )


# --- Error #55 ---


class MixedCloudMultiStateError(ComplyFormError):
    """Multi-state scan mixes cloud providers."""

    def __init__(self, clouds_detected: set[str]) -> None:
        super().__init__(
            title="Mixed Clouds in Multi-State Scan",
            message=(
                f"Mixed clouds detected:"
                f" {', '.join(sorted(clouds_detected))}."
                " Multi-state scanning requires all states"
                " to use the same cloud provider."
            ),
            fix="Use `--batch` with per-project cloud instead of multi-state.",
            error_code=55,
        )


# --- Error #56 ---


class DiscoverNoStatesFoundError(ComplyFormError):
    """No state files found during discovery."""

    def __init__(self, directory: str, pattern: str) -> None:
        super().__init__(
            title="No State Files Found",
            message=(f"No state files matching '{pattern}' found in '{directory}'."),
            fix="Check the directory path and pattern. Default pattern: *.tfstate",
            error_code=56,
        )


# --- Error #57 ---


class DiscoverTooManyStatesError(ComplyFormError):
    """Too many state files discovered for tier limit."""

    def __init__(self, count: int, limit: int, directory: str) -> None:
        super().__init__(
            title="Too Many State Files",
            message=(f"Found {count} state files in '{directory}' (limit: {limit})."),
            fix="Use `--batch` with a manifest or narrow the discovery directory.",
            error_code=57,
        )


# --- Error #58 ---


class StateDiscoverConflictError(ComplyFormError):
    """Cannot use --state and --discover together."""

    def __init__(self) -> None:
        super().__init__(
            title="Conflicting Flags",
            message="Cannot use --state and --discover together.",
            fix="Use --state for explicit paths or --discover"
            " for directory scanning, not both.",
            error_code=58,
        )


# --- Error #59 ---


class BatchFlagConflictError(ComplyFormError):
    """Cannot use --batch with --state or --discover."""

    def __init__(self) -> None:
        super().__init__(
            title="Conflicting Flags",
            message="Cannot use --batch with --state or --discover.",
            fix="Use --batch alone for multi-project operations.",
            error_code=59,
        )


# --- Error #60 ---


class BatchAssessTierError(ComplyFormError):
    """Batch scan/assess requires Pro tier."""

    def __init__(self, tier: str) -> None:
        super().__init__(
            title="Batch Scan/Assess Requires Pro Tier",
            message=(f"Batch scan/assess requires Pro tier (current: {tier})."),
            fix="Upgrade at complyform.dev/pricing",
            error_code=60,
        )


# --- Error #18 ---


class BatchManifestNotFoundError(ComplyFormError):
    """Batch manifest file not found."""

    def __init__(self, path: str) -> None:
        super().__init__(
            title="Batch Manifest Not Found",
            message=f"Batch manifest file not found: {path}",
            fix="Check the file path. Expected YAML file with batch-manifest schema.",
            error_code=18,
        )


# --- Error #19 ---


class BatchManifestInvalidError(ComplyFormError):
    """Batch manifest YAML or schema is invalid."""

    def __init__(self, detail: str) -> None:
        super().__init__(
            title="Invalid Batch Manifest",
            message=f"Batch manifest validation failed: {detail}",
            fix="Check the YAML syntax and schema. See docs: complyform.dev/docs/batch",
            error_code=19,
        )


# --- Error #20 ---


class BatchDuplicateLabelsError(ComplyFormError):
    """Duplicate project labels in batch manifest."""

    def __init__(self, dupes: list[str]) -> None:
        super().__init__(
            title="Duplicate Labels in Batch Manifest",
            message=(
                f"Duplicate project labels:"
                f" {', '.join(dupes)}."
                " Each label must be unique."
            ),
            fix="Rename duplicate labels in the batch manifest.",
            error_code=20,
        )


# --- Error #69 ---


class ExplainNoAssessmentError(ComplyFormError):
    """No cached assessment available for explain."""

    def __init__(self) -> None:
        super().__init__(
            title="No Assessment Found",
            message="No cached assessment found. Run `complyform assess` first.",
            fix=(
                "complyform scan --state=./terraform.tfstate"
                " && complyform assess --frameworks=<id>"
            ),
            error_code=69,
        )


# --- Error #70 ---


class ExplainFindingNotFoundError(ComplyFormError):
    """Requested finding not found in cached assessment."""

    def __init__(self, target: str) -> None:
        super().__init__(
            title="Finding Not Found",
            message=f"Finding '{target}' not found in cached assessment.",
            fix=("Run `complyform assess` to refresh, or check the control ID."),
            error_code=70,
        )


# --- Error #71 ---


class AIExplainTierError(ComplyFormError):
    """AI-enhanced explain requires Pro+ tier."""

    def __init__(self, tier: str) -> None:
        super().__init__(
            title="AI Explain Requires Pro Tier",
            message=(f"AI-enhanced explanations require Pro tier (current: {tier})."),
            fix="Upgrade at https://complyform.dev/pricing",
            error_code=71,
        )


# --- Error #72 ---


class AIExplainKeyMissingError(ComplyFormError):
    """Anthropic API key not configured."""

    def __init__(self) -> None:
        super().__init__(
            title="Anthropic API Key Missing",
            message=(
                "AI-enhanced explanations require an Anthropic API key."
                " Set it in config or ANTHROPIC_API_KEY environment variable."
            ),
            fix="complyform config set anthropic_api_key <key>",
            error_code=72,
        )


# --- Error #73 ---


class AIExplainAPIError(ComplyFormError):
    """Anthropic API call failed."""

    def __init__(self, status: int, detail: str) -> None:
        super().__init__(
            title="AI Explain API Error",
            message=(
                f"Anthropic API error ({status}): {detail}."
                " Falling back to deterministic explanation."
            ),
            fix="Check your API key and network connection.",
            error_code=73,
        )


# --- Error #76 ---


class ExplainAmbiguousControlError(ComplyFormError):
    """Control ID matches multiple frameworks."""

    def __init__(self, control_id: str, frameworks: list[str]) -> None:
        fw_list = ", ".join(frameworks)
        super().__init__(
            title="Ambiguous Control ID",
            message=(
                f"Control '{control_id}' exists in multiple frameworks: {fw_list}."
            ),
            fix="Use --framework to specify which framework.",
            error_code=76,
        )


# --- Error #61 ---


class PRTierError(ComplyFormError):
    """PR creation requires Pro+ tier."""

    def __init__(self, tier: str) -> None:
        super().__init__(
            title="PR Creation Requires Pro Tier",
            message=f"Current tier: {tier}",
            fix="Upgrade at complyform.dev/pricing",
            error_code=61,
        )


# --- Error #62 ---


class PRTokenMissingError(ComplyFormError):
    """GitHub token not configured."""

    def __init__(self) -> None:
        super().__init__(
            title="GitHub Token Not Configured",
            message="No GitHub token found in config.",
            fix=(
                "Set GitHub token:"
                " `complyform config set github_token=ghp_...`"
                " Requires fine-grained PAT with"
                " Contents:write + Pull-requests:write."
            ),
            error_code=62,
        )


# --- Error #63 ---


class PRTokenInvalidError(ComplyFormError):
    """GitHub token validation failed."""

    def __init__(self, status: int, detail: str) -> None:
        super().__init__(
            title="GitHub Token Validation Failed",
            message=(f"GitHub token validation failed ({status}): {detail}"),
            fix=(
                "Generate a new fine-grained PAT at"
                " github.com/settings/tokens?type=beta"
            ),
            error_code=63,
        )


# --- Error #64 ---


class PRRepoDetectionError(ComplyFormError):
    """Could not detect GitHub repo from git remote."""

    def __init__(self) -> None:
        super().__init__(
            title="GitHub Repo Not Detected",
            message=("Could not detect GitHub repo from git remote."),
            fix="Use `--pr-repo=owner/repo` to specify.",
            error_code=64,
        )


# --- Error #65 ---


class PRBranchExistsError(ComplyFormError):
    """Branch already exists on remote."""

    def __init__(self, branch: str) -> None:
        super().__init__(
            title="Branch Already Exists",
            message=(f"Branch '{branch}' already exists on remote."),
            fix=(
                "Use `--pr-branch=<name>` to specify a"
                " different branch, or delete the"
                " existing branch."
            ),
            error_code=65,
        )


# --- Error #66 ---


class PRCreationError(ComplyFormError):
    """GitHub PR creation failed."""

    def __init__(self, status: int, detail: str) -> None:
        super().__init__(
            title="GitHub PR Creation Failed",
            message=f"GitHub API error ({status}): {detail}",
            fix="Check token permissions and repo access.",
            error_code=66,
        )


# --- Error #67 ---


class PRNoPatchesError(ComplyFormError):
    """No patches were generated — nothing to commit."""

    def __init__(self) -> None:
        super().__init__(
            title="No Patches to PR",
            message=("No patches were generated — nothing to commit."),
            fix=("Run `complyform assess` to check for compliance gaps first."),
            error_code=67,
        )


# --- Error #68 ---


class PRFlagConflictError(ComplyFormError):
    """PR flag used without --open-pr."""

    def __init__(self, flag: str) -> None:
        super().__init__(
            title="Flag Requires --open-pr",
            message=f"--{flag} requires --open-pr.",
            fix=f"Add `--open-pr` or remove `--{flag}`.",
            error_code=68,
        )


# --- Error #37 ---


class UnknownServiceError(ComplyFormError):
    """One or more service IDs are not recognized for the selected cloud."""

    def __init__(self, service_ids: list[str], cloud: str) -> None:
        ids_str = ", ".join(service_ids)
        super().__init__(
            title="Unknown Service",
            message=f"Unknown service(s) for {cloud}: {ids_str}.",
            fix=f"Run `complyform init` to see available services for {cloud}.",
            error_code=37,
        )


# --- Error #38 ---


class InvalidProjectIdError(ComplyFormError):
    """Project ID does not match the expected format for the cloud."""

    def __init__(self, project_id: str, cloud: str) -> None:
        hints: dict[str, str] = {
            "gcp": "GCP project IDs: 6-30 chars, lowercase letters, digits, hyphens.",
            "aws": "AWS account IDs: exactly 12 digits.",
            "azure": "Azure subscription IDs: UUID v4 format.",
        }
        super().__init__(
            title="Invalid Project ID",
            message=f"Invalid project ID '{project_id}' for {cloud}.",
            fix="Check your project/account/subscription ID format.",
            error_code=38,
            hint=hints.get(cloud, ""),
        )


# --- Error #49 ---


class InvalidOrgIdError(ComplyFormError):
    """Organization ID is not numeric."""

    def __init__(self, org_id: str) -> None:
        super().__init__(
            title="Invalid Organization ID",
            message=f"Organization ID must be numeric, got: '{org_id}'.",
            fix="Provide a numeric organization ID or omit it.",
            error_code=49,
        )


# --- Error #77 ---


class InitConfigNotFoundError(ComplyFormError):
    """complyform.init.yaml not found."""

    def __init__(self, path: str) -> None:
        super().__init__(
            title="Init Config Not Found",
            message=(
                f"Config file not found: '{path}'."
                " Run 'complyform init' first, or specify '--config=path'."
            ),
            fix="complyform init",
            error_code=77,
        )


# --- Error #78 ---


class ConfigMergeConflictError(ComplyFormError):
    """Cannot auto-merge conflicting framework requirements."""

    def __init__(
        self, attribute: str, frameworks: list[str], values: list[str]
    ) -> None:
        fw_str = ", ".join(frameworks)
        val_str = ", ".join(values)
        super().__init__(
            title="Config Merge Conflict",
            message=(
                f"Cannot auto-merge '{attribute}':"
                f" frameworks [{fw_str}] require different values [{val_str}]."
                " Generate for one framework at a time,"
                " or file a GitHub issue."
            ),
            fix="complyform generate --config=... (with a single framework)",
            error_code=78,
        )


# --- Error #79 ---


class OutputDirectoryNotEmptyError(ComplyFormError):
    """Output directory is not empty and --force not specified."""

    def __init__(self, path: str) -> None:
        super().__init__(
            title="Output Directory Not Empty",
            message=(
                f"Output directory '{path}' is not empty."
                " Use '--force' to overwrite,"
                " or choose a different '--output' directory."
            ),
            fix=f"complyform generate --force --config=... --output={path}",
            error_code=79,
        )


# --- Error #80 ---


class DependencyCycleError(ComplyFormError):
    """Dependency cycle detected in profile mappings."""

    def __init__(self, cycle: list[str]) -> None:
        cycle_str = " → ".join(cycle)
        super().__init__(
            title="Dependency Cycle Detected",
            message=(
                f"Dependency cycle detected: {cycle_str}."
                " This is a bug in the profile mappings"
                " — please report via 'complyform feedback --bug'."
            ),
            fix="complyform feedback --bug",
            error_code=80,
        )


# --- Error #81 (greenfield) ---


class ServiceCatalogNotFoundError(ComplyFormError):
    """Service catalog YAML not found for cloud."""

    def __init__(self, cloud: str) -> None:
        super().__init__(
            title="Service Catalog Not Found",
            message=(
                f"Service catalog not found for cloud '{cloud}'."
                " This may indicate a corrupted installation"
                " — reinstall with 'uv tool install complyform'."
            ),
            fix="uv tool install --force complyform",
            error_code=86,
        )


# --- Error #82 ---


class TemplateNotFoundError(ComplyFormError):
    """Jinja2 template not found for cloud."""

    def __init__(self, template_name: str, cloud: str) -> None:
        super().__init__(
            title="Template Not Found",
            message=(
                f"Template '{template_name}' not found for cloud '{cloud}'."
                " This may indicate a corrupted installation."
            ),
            fix="uv tool install --force complyform",
            error_code=82,
        )


# --- Error #83 ---


class TemplateRenderError(ComplyFormError):
    """Jinja2 template rendering failed."""

    def __init__(self, template_name: str, detail: str) -> None:
        super().__init__(
            title="Template Render Error",
            message=f"Failed to render template '{template_name}': {detail}",
            fix="Check template syntax or report a bug.",
            error_code=83,
        )


# --- Error #84 ---


class InitMissingFlagsError(ComplyFormError):
    """Non-interactive mode requires all flags."""

    def __init__(self, missing: list[str]) -> None:
        missing_str = ", ".join(missing)
        super().__init__(
            title="Missing Required Flags",
            message=(
                f"Non-interactive mode requires all flags."
                f" Missing: {missing_str}."
                " Run 'complyform init' interactively or provide all flags."
            ),
            fix=(
                "complyform init --cloud=... --project=..."
                " --frameworks=... --services=... --region=..."
            ),
            error_code=84,
        )


# --- Error #85 ---


class InitConfigVersionError(ComplyFormError):
    """complyform.init.yaml version is unsupported."""

    def __init__(self, found_version: str, supported: str) -> None:
        super().__init__(
            title="Init Config Version Mismatch",
            message=(
                f"complyform.init.yaml version '{found_version}'"
                f" is not supported (expected '{supported}')."
                " Regenerate with 'complyform init'."
            ),
            fix="complyform init",
            error_code=85,
        )


# --- Error #31 ---


class PolicyGenTierError(ComplyFormError):
    """Policy generation format requires a higher tier."""

    def __init__(self, format: str, tier: str) -> None:
        super().__init__(
            title="Policy Generation Tier Required",
            message=f"{format} generation requires a higher tier",
            fix=(
                "OPA/Rego requires Team+. Custom Checkov and GitHub Action"
                f" require Pro+. Your tier: {tier}."
                " Upgrade at complyform.dev/pricing"
            ),
            error_code=31,
        )


# --- Error #32 ---


class PolicyGenOverwriteError(ComplyFormError):
    """Output directory already exists and --overwrite not set."""

    def __init__(self, path: str) -> None:
        super().__init__(
            title="Output Directory Exists",
            message=f"Output directory already exists: {path}",
            fix=(
                "Use --overwrite to replace existing files,"
                " or choose a different --output directory"
            ),
            error_code=32,
        )


# --- Error #33 ---


class PolicyGenNoFindingsError(ComplyFormError):
    """No compliance gaps found — all controls pass."""

    def __init__(self) -> None:
        super().__init__(
            title="No Compliance Gaps",
            message="No compliance gaps found",
            fix="All controls pass — no policies to generate. This is a success state.",
            error_code=33,
        )

    def display(self, console: Console, *, style: str = "green") -> None:
        """Render as a green success panel."""
        body = self.message
        body += f"\n\nFix: {self.fix}"
        if self.hint:
            body += f"\n{self.hint}"
        console.print(Panel(body, title=f"✓ {self.title}", style=style))


# --- Error #40 ---


class ExportCredentialsMissingError(ComplyFormError):
    """Export credentials not provided for GRC platform push."""

    def __init__(self, platform: str, required_flags: list[str]) -> None:
        flags = ", ".join(required_flags)
        super().__init__(
            title="Export Credentials Missing",
            message=(
                f"No credentials provided for {platform} API push. Required: {flags}"
            ),
            fix=(
                f"Provide credentials via CLI flags ({flags})"
                f" or environment variables. Use --file-only to skip API push."
            ),
            error_code=40,
        )


# --- Error #41 ---


class ExportAPIPushError(ComplyFormError):
    """GRC API push failed."""

    def __init__(self, platform: str, detail: str) -> None:
        super().__init__(
            title="Export API Error",
            message=f"API push to {platform} failed: {detail}",
            fix=(
                "Check credentials and network connectivity."
                " Use --file-only to export without API push."
            ),
            error_code=41,
        )


# --- Error #44 ---


class ExportAPIAuthError(ComplyFormError):
    """GRC API authentication failed."""

    def __init__(self, platform: str) -> None:
        super().__init__(
            title="Export API Error",
            message=f"Authentication failed for {platform} API. Check credentials.",
            fix="Verify your API key/secret and try again.",
            error_code=44,
        )


# --- Error #45 ---


class ExportAPIRateLimitError(ComplyFormError):
    """GRC API rate limit exceeded."""

    def __init__(self, platform: str) -> None:
        super().__init__(
            title="Export API Error",
            message=f"Rate limit exceeded for {platform} API after retries.",
            fix="Wait a few minutes and try again.",
            error_code=45,
        )


# --- Error #46 ---


class ExportAPIError(ComplyFormError):
    """Generic GRC API error."""

    def __init__(self, platform: str, status: int, detail: str) -> None:
        super().__init__(
            title="Export API Error",
            message=f"{platform} API returned status {status}: {detail}",
            fix="Check the error details and try again.",
            error_code=46,
        )


# --- Error #39 ---


class ExportFormatError(ComplyFormError):
    """Unknown export format specified."""

    def __init__(self, format_id: str, valid_formats: list[str]) -> None:
        super().__init__(
            title="Unknown Export Format",
            message=f"Unknown export format: {format_id}",
            fix=f"Valid formats: {', '.join(valid_formats)}",
            error_code=39,
        )


# --- Error #42 ---


class ExportTierError(ComplyFormError):
    """Export format requires a higher tier."""

    def __init__(
        self,
        format_id: str,
        required_feature: str,
        current_tier: str,
    ) -> None:
        super().__init__(
            title="Export Tier Required",
            message=f"Export format '{format_id}' requires a higher tier",
            fix=(
                "Evidence export requires Team tier for cloud-native formats"
                " or Agency tier for GRC platform formats."
                f" Your current tier: {current_tier}."
                " Upgrade at complyform.dev/pricing"
            ),
            error_code=42,
        )


# --- Error #43 ---


class ExportNoCachedAssessmentError(ComplyFormError):
    """No cached assessment available for export."""

    def __init__(self) -> None:
        super().__init__(
            title="No Cached Assessment",
            message="No cached assessment to export",
            fix="Run 'complyform scan --assess' first, then export",
            error_code=43,
        )


# --- Error #87 ---


class LicenseResendRateLimitError(ComplyFormError):
    """License key resend rate limit exceeded."""

    def __init__(self) -> None:
        super().__init__(
            title="Resend Rate Limit Exceeded",
            message="Too many license key resend requests. Try again later.",
            fix="Wait at least 1 hour before requesting another resend.",
            error_code=87,
        )


# --- Error #88 ---


class ProjectLimitError(ComplyFormError):
    """Project limit reached for current tier."""

    def __init__(self, limit: int, tier: str) -> None:
        super().__init__(
            title="Project Limit Reached",
            message=f"Your {tier} tier allows {limit} project(s). Limit reached.",
            fix="Upgrade at https://complyform.dev/pricing",
            error_code=88,
        )


# --- Error #89 ---


class OverageSuspendedError(ComplyFormError):
    """Project suspended due to unpaid overage invoice."""

    def __init__(self, project_label: str, tier: str) -> None:
        super().__init__(
            title="Project Suspended — Overage Unpaid",
            message=(
                f"Project '{project_label}' is suspended on the {tier} tier"
                " due to an unpaid overage invoice."
            ),
            fix="Pay the outstanding invoice at https://complyform.dev/billing",
            error_code=89,
        )


# --- Error #92 ---


class ThemePreferenceError(ComplyFormError):
    """Invalid theme preference value on PUT."""

    def __init__(self, value: str) -> None:
        super().__init__(
            title="Invalid Theme Preference",
            message=(f"Invalid theme preference '{value}'. Valid values: light, dark."),
            fix="Use 'light' or 'dark' as the theme value.",
            error_code=92,
        )


# --- Error #93 ---


class ConsentVersionError(ComplyFormError):
    """Consent version mismatch (defensive)."""

    def __init__(self, current: str, required: str) -> None:
        super().__init__(
            title="Consent Version Mismatch",
            message=(
                f"Consent version mismatch. Current: {current}, required: {required}."
            ),
            fix="Accept the latest terms at /dashboard/.",
            error_code=93,
        )


# --- Error #94 ---


class SharedScanPasswordError(ComplyFormError):
    """Incorrect password on shared link."""

    def __init__(self) -> None:
        super().__init__(
            title="Incorrect Password",
            message="Incorrect password. Please try again.",
            fix="Enter the correct password provided by the project owner.",
            error_code=94,
        )


# --- Error #95 ---


class ShareLinkExpiredError(ComplyFormError):
    """Share link expired or revoked."""

    def __init__(self, token_prefix: str) -> None:
        super().__init__(
            title="Share Link Expired",
            message=(
                "This shared link has expired."
                " Request a new link from the project owner."
            ),
            fix="Contact the project owner for a new share link.",
            error_code=95,
        )


# --- Error #96 ---


class ShareLinkNotFoundError(ComplyFormError):
    """Token hash not found."""

    def __init__(self) -> None:
        super().__init__(
            title="Share Link Not Found",
            message=(
                "Shared link not found."
                " It may have been revoked or the URL is incorrect."
            ),
            fix="Check the URL or request a new link from the project owner.",
            error_code=96,
        )


# --- Error #97 ---


class ShareLinkLimitError(ComplyFormError):
    """Max 10 active links per project."""

    def __init__(self, project_label: str, limit: int) -> None:
        super().__init__(
            title="Share Link Limit Reached",
            message=(
                f"Project '{project_label}' has {limit} active share links."
                " Revoke an existing link first."
            ),
            fix="Revoke an unused share link, then create a new one.",
            error_code=97,
        )


# --- Error #98 ---


class RemoteStateAuthError(ComplyFormError):
    """Remote state backend authentication failure."""

    def __init__(self, backend: str, detail: str) -> None:
        super().__init__(
            title="Remote State Authentication Failed",
            message=(
                f"Authentication failed for {backend}: {detail}."
                " Check credentials — run `complyform doctor` for diagnosis."
            ),
            fix="complyform doctor",
            error_code=98,
        )


# --- Error #99 ---


class RemoteStateNotFoundError(ComplyFormError):
    """State file not found at remote URI."""

    def __init__(self, uri: str) -> None:
        super().__init__(
            title="Remote State Not Found",
            message=(
                f"State file not found at '{uri}'."
                " Verify the path and check bucket/container permissions."
            ),
            fix="complyform scan --state=<correct-path>",
            error_code=99,
        )


# --- Error #100 ---


class RemoteStateNetworkError(ComplyFormError):
    """Network/timeout error fetching remote state."""

    def __init__(self, backend: str, detail: str) -> None:
        super().__init__(
            title="Remote State Network Error",
            message=(
                f"Network error fetching state from {backend}: {detail}."
                " Check connectivity and try again."
            ),
            fix="complyform doctor",
            error_code=100,
        )


# --- Error #101 ---


class RemoteStateUnsupportedError(ComplyFormError):
    """Unrecognized remote state URI scheme."""

    def __init__(self, scheme: str) -> None:
        super().__init__(
            title="Unsupported Remote State Scheme",
            message=(
                f"Unsupported remote state scheme '{scheme}'."
                " Supported: gs://, s3://, azure://, app.terraform.io"
            ),
            fix="complyform scan --state=gs://bucket/path OR s3://bucket/path",
            error_code=101,
        )


# --- Error #102 ---


class SSONotConfiguredError(ComplyFormError):
    """No SSO configuration for email domain."""

    def __init__(self, email_domain: str) -> None:
        super().__init__(
            title="SSO Not Configured",
            message=(
                f"No SSO configuration for domain '{email_domain}'."
                " Contact IT admin or sign in with Google."
            ),
            fix="Dashboard → Settings → SSO",
            error_code=102,
        )


# --- Error #103 ---


class SSOAuthenticationFailedError(ComplyFormError):
    """SSO assertion validation failed."""

    def __init__(self, reason: str) -> None:
        super().__init__(
            title="SSO Authentication Failed",
            message=(
                f"SSO authentication failed: {reason}."
                " Check IdP config in Dashboard → Settings → SSO."
            ),
            fix="Dashboard → Settings → SSO",
            error_code=103,
        )


# --- Error #104 ---


class SSOEnforcedError(ComplyFormError):
    """SSO enforced, Google OAuth blocked."""

    def __init__(self, email_domain: str) -> None:
        super().__init__(
            title="SSO Enforced",
            message=(
                f"SSO enforced for '{email_domain}'."
                " Google sign-in unavailable."
                " Use organization's SSO provider."
            ),
            fix="Contact IT admin for SSO credentials",
            error_code=104,
        )


# --- Error #105 ---


class SCIMWebhookValidationError(ComplyFormError):
    """SCIM webhook signature invalid."""

    def __init__(self, reason: str) -> None:
        super().__init__(
            title="SCIM Webhook Validation Failed",
            message=(
                f"SCIM webhook signature invalid: {reason}."
                " Verify broker webhook secret in"
                " Dashboard → Settings → SCIM."
            ),
            fix="Dashboard → Settings → SCIM",
            error_code=105,
        )


# --- Error #106 ---


class DataExportRateLimitError(ComplyFormError):
    """Export requested within 24h of last export."""

    def __init__(self) -> None:
        super().__init__(
            title="Data Export Rate Limited",
            message=(
                "Data export already requested in the last 24 hours. Try again later."
            ),
            fix="Wait 24 hours before requesting another export.",
            error_code=106,
        )


# --- Error #107 ---


class GotenbergPayloadTooLargeError(ComplyFormError):
    """HTML payload exceeds the 10 MB PDF rendering limit."""

    def __init__(self, size_bytes: int) -> None:
        size_mb = size_bytes / (1024 * 1024)
        super().__init__(
            title="PDF Generation Error",
            message=(f"Report too large for PDF ({size_mb:.1f}MB, max 10MB)."),
            fix="Use `--format=html` and print to PDF from browser.",
            error_code=107,
        )


# --- Error #108 ---


class GotenbergTimeoutError(ComplyFormError):
    """PDF rendering timed out."""

    def __init__(self) -> None:
        super().__init__(
            title="PDF Generation Error",
            message="PDF rendering timed out.",
            fix="Use `--format=html` as workaround.",
            error_code=108,
        )


# --- Error #109 ---


class GotenbergRenderError(ComplyFormError):
    """PDF rendering failed with an HTTP error."""

    def __init__(self, status_code: int, detail: str) -> None:
        super().__init__(
            title="PDF Generation Error",
            message=f"PDF rendering failed ({status_code}): {detail}",
            fix="Use `--format=html` as workaround.",
            error_code=109,
        )


# ---------------------------------------------------------------------------
# Cloud Intelligence errors (Phase 19a)
# ---------------------------------------------------------------------------


class CloudIntelligenceAPIError(ComplyFormError):
    """Cloud intelligence API returned an error."""

    def __init__(self, feature: str, cloud: str, detail: str) -> None:
        super().__init__(
            title="Cloud intelligence API error",
            message=f"{feature} unavailable on {cloud}: {detail}.",
            fix=(
                f"\u26a0 {feature} unavailable on {cloud}:"
                f" {detail}. Continuing without."
            ),
            error_code=25,
        )


class CloudIntelligencePermissionError(ComplyFormError):
    """Missing cloud intelligence permission."""

    _FIX_MAP: dict[str, str] = {
        "gcp": (
            "Grant {permission} \u2014 run: gcloud projects add-iam-policy-binding"
            " {project} --member=user:{{email}} --role={permission}"
        ),
        "aws": (
            "Attach policy with {permission}"
            " \u2014 see docs.complyform.dev/permissions/aws"
        ),
        "azure": (
            "Assign {permission} role \u2014 run: az role assignment create"
            " --assignee {{principal}} --role '{permission}'"
            " --scope /subscriptions/{{sub}}"
        ),
    }

    def __init__(self, feature: str, cloud: str, permission: str) -> None:
        template = self._FIX_MAP.get(cloud, "Grant {permission} for {cloud}")
        fix = template.format(permission=permission, cloud=cloud, project="{project}")
        super().__init__(
            title="Missing cloud intelligence permission",
            message=f"{feature} on {cloud} requires {permission}.",
            fix=fix,
            error_code=26,
        )


class CloudIntelligenceAPINotEnabledError(ComplyFormError):
    """Cloud API not enabled."""

    _FIX_MAP: dict[str, str] = {
        "gcp": (
            "Enable {api} \u2014 run: gcloud services enable {api} --project={project}"
        ),
        "aws": (
            "Enable {api} in AWS Console \u2014 see docs.complyform.dev/permissions/aws"
        ),
        "azure": (
            "Register {api} resource provider \u2014 run:"
            " az provider register --namespace {api}"
        ),
    }

    def __init__(self, api: str, cloud: str, project: str) -> None:
        template = self._FIX_MAP.get(cloud, "Enable {api} for {cloud}")
        fix = template.format(api=api, cloud=cloud, project=project)
        super().__init__(
            title="Cloud API not enabled",
            message=f"{api} is not enabled on {cloud} project {project}.",
            fix=fix,
            error_code=27,
        )


class IAMAnalyzerNotFoundError(ComplyFormError):
    """IAM analyzer not found."""

    def __init__(self, cloud: str) -> None:
        super().__init__(
            title="IAM analyzer not found",
            message=f"No IAM analyzer found for {cloud}.",
            fix=(
                "Create an unused access analyzer: aws accessanalyzer"
                " create-analyzer --analyzer-name complyform"
                " --type ACCOUNT_UNUSED_ACCESS"
            ),
            error_code=28,
        )


class InfracostExecutionError(ComplyFormError):
    """Infracost execution failed."""

    def __init__(self, stderr: str, exit_code: int) -> None:
        stderr_snippet = stderr[:200] if stderr else "unknown error"
        super().__init__(
            title="Infracost execution failed",
            message=f"Infracost exited with code {exit_code}.",
            fix=(
                f"Infracost error (exit {exit_code}): {stderr_snippet}."
                " Falling back to pricing API estimates."
            ),
            error_code=29,
        )
