"""
AIPTX Key Validation - Base Classes
====================================

Core data structures and base validator interface for API key validation.

This module provides:
- KeyType enum for all supported key types
- APIKeyInfo dataclass for parsed key information
- ValidationResult dataclass for validation outcomes
- BaseKeyValidator abstract class for implementing validators
"""

from __future__ import annotations

import re
import hashlib
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from datetime import datetime, timezone
from enum import Enum
from typing import Any, Optional, TYPE_CHECKING

if TYPE_CHECKING:
    from aipt_v2.models.findings import Finding


class KeyType(str, Enum):
    """Supported API key types."""

    # Cloud Providers
    AWS = "aws"
    AWS_SESSION = "aws_session"
    GCP = "gcp"
    GCP_API_KEY = "gcp_api_key"
    AZURE = "azure"
    AZURE_SAS = "azure_sas"

    # Version Control
    GITHUB = "github"
    GITHUB_FINE_GRAINED = "github_fine_grained"
    GITLAB = "gitlab"
    BITBUCKET = "bitbucket"

    # Communication
    SLACK = "slack"
    SLACK_WEBHOOK = "slack_webhook"
    DISCORD = "discord"
    TWILIO = "twilio"
    SENDGRID = "sendgrid"
    MAILGUN = "mailgun"

    # Payment
    STRIPE = "stripe"
    STRIPE_RESTRICTED = "stripe_restricted"
    PAYPAL = "paypal"
    SQUARE = "square"

    # Cloud Storage
    S3_BUCKET = "s3_bucket"
    GCS_BUCKET = "gcs_bucket"
    AZURE_BLOB = "azure_blob"

    # Atlassian
    ATLASSIAN = "atlassian"
    JIRA = "jira"
    CONFLUENCE = "confluence"

    # Other SaaS
    OPENAI = "openai"
    ANTHROPIC = "anthropic"
    DATADOG = "datadog"
    SENTRY = "sentry"
    FIREBASE = "firebase"
    HEROKU = "heroku"
    DIGITALOCEAN = "digitalocean"
    NPM = "npm"
    PYPI = "pypi"
    DOCKER_HUB = "docker_hub"

    # Generic
    GENERIC_API_KEY = "generic_api_key"
    GENERIC_TOKEN = "generic_token"
    JWT = "jwt"
    OAUTH_TOKEN = "oauth_token"
    PRIVATE_KEY = "private_key"

    # Database
    DATABASE_URL = "database_url"
    MONGODB = "mongodb"
    REDIS = "redis"


class RiskLevel(str, Enum):
    """Risk level classification for validated keys."""
    CRITICAL = "critical"
    HIGH = "high"
    MEDIUM = "medium"
    LOW = "low"
    INFO = "info"


@dataclass
class APIKeyInfo:
    """
    Parsed API key information.

    Contains all the details about a discovered API key,
    including its type, value, and associated metadata.
    """

    # Key identification
    key_type: KeyType
    key_value: str

    # Optional additional credentials (e.g., AWS needs both access_key and secret)
    key_id: Optional[str] = None
    secret_value: Optional[str] = None
    session_token: Optional[str] = None

    # Context
    region: Optional[str] = None
    account_id: Optional[str] = None
    domain: Optional[str] = None  # For Atlassian, custom GitLab, etc.

    # Source tracking
    source_finding: Optional[Any] = None  # Finding object
    source_file: Optional[str] = None
    source_line: Optional[int] = None
    source_url: Optional[str] = None

    # Detection info
    detected_pattern: Optional[str] = None
    confidence: float = 1.0

    # Metadata
    metadata: dict = field(default_factory=dict)
    discovered_at: str = field(
        default_factory=lambda: datetime.now(timezone.utc).isoformat()
    )

    def __post_init__(self):
        """Generate fingerprint for deduplication."""
        self._fingerprint = self._generate_fingerprint()

    def _generate_fingerprint(self) -> str:
        """Generate unique fingerprint for this key."""
        # Use key value and type for fingerprint
        data = f"{self.key_type.value}:{self.key_value}"
        if self.key_id:
            data += f":{self.key_id}"
        return hashlib.sha256(data.encode()).hexdigest()[:16]

    @property
    def fingerprint(self) -> str:
        """Get unique fingerprint."""
        return self._fingerprint

    @property
    def masked_value(self) -> str:
        """Get masked version of the key for display."""
        if len(self.key_value) <= 8:
            return "*" * len(self.key_value)
        return self.key_value[:4] + "*" * (len(self.key_value) - 8) + self.key_value[-4:]

    def to_dict(self) -> dict:
        """Convert to dictionary for serialization."""
        return {
            "key_type": self.key_type.value,
            "key_value_masked": self.masked_value,
            "key_id": self.key_id,
            "region": self.region,
            "account_id": self.account_id,
            "domain": self.domain,
            "confidence": self.confidence,
            "fingerprint": self.fingerprint,
            "discovered_at": self.discovered_at,
            "metadata": self.metadata,
        }


@dataclass
class ValidationResult:
    """
    Result of API key validation.

    Contains all information about a validation attempt,
    including whether the key is valid, what permissions it has,
    and what resources are accessible.
    """

    # Source key
    key_info: APIKeyInfo

    # Validation status
    is_valid: bool
    validation_method: str = ""
    validation_endpoint: str = ""

    # Identity information (who does this key belong to?)
    identity_info: dict = field(default_factory=dict)

    # Permissions and access
    permissions: list = field(default_factory=list)
    scopes: list = field(default_factory=list)
    resources_accessible: list = field(default_factory=list)

    # Risk assessment
    risk_level: RiskLevel = RiskLevel.INFO
    exploitation_potential: str = ""
    attack_vectors: list = field(default_factory=list)

    # Raw API response
    raw_response: dict = field(default_factory=dict)

    # Error information
    error: Optional[str] = None
    error_code: Optional[str] = None

    # Timestamps
    validated_at: str = field(
        default_factory=lambda: datetime.now(timezone.utc).isoformat()
    )
    validation_duration_ms: float = 0.0

    def to_dict(self) -> dict:
        """Convert to dictionary for serialization."""
        return {
            "key_info": self.key_info.to_dict(),
            "is_valid": self.is_valid,
            "validation_method": self.validation_method,
            "identity_info": self.identity_info,
            "permissions": self.permissions,
            "scopes": self.scopes,
            "resources_accessible": self.resources_accessible[:10],  # Limit for display
            "risk_level": self.risk_level.value,
            "exploitation_potential": self.exploitation_potential,
            "attack_vectors": self.attack_vectors,
            "error": self.error,
            "validated_at": self.validated_at,
            "validation_duration_ms": self.validation_duration_ms,
        }

    def to_finding_dict(self) -> dict:
        """Convert to Finding-compatible dictionary."""
        severity_map = {
            RiskLevel.CRITICAL: "critical",
            RiskLevel.HIGH: "high",
            RiskLevel.MEDIUM: "medium",
            RiskLevel.LOW: "low",
            RiskLevel.INFO: "info",
        }

        return {
            "title": f"Valid {self.key_info.key_type.value.upper()} API Key",
            "severity": severity_map[self.risk_level],
            "type": "credential_found",
            "value": self.key_info.masked_value,
            "description": self._build_description(),
            "evidence": self._build_evidence(),
            "remediation": self._build_remediation(),
            "confirmed": True,
            "exploited": True,
        }

    def _build_description(self) -> str:
        """Build description for finding."""
        parts = [
            f"A valid {self.key_info.key_type.value} API key was discovered and validated.",
        ]

        if self.identity_info:
            if "user" in self.identity_info:
                parts.append(f"Identity: {self.identity_info['user']}")
            if "account" in self.identity_info:
                parts.append(f"Account: {self.identity_info['account']}")

        if self.permissions:
            parts.append(f"Permissions: {len(self.permissions)} discovered")

        if self.exploitation_potential:
            parts.append(f"Exploitation: {self.exploitation_potential}")

        return " | ".join(parts)

    def _build_evidence(self) -> str:
        """Build evidence string."""
        import json
        evidence = {
            "key_prefix": self.key_info.key_value[:12] + "...",
            "identity": self.identity_info,
            "permissions_sample": self.permissions[:5] if self.permissions else [],
            "validation_endpoint": self.validation_endpoint,
        }
        return json.dumps(evidence, indent=2)

    def _build_remediation(self) -> str:
        """Build remediation guidance."""
        return (
            f"1. Immediately rotate the exposed {self.key_info.key_type.value} credential\n"
            f"2. Audit access logs for unauthorized usage\n"
            f"3. Review and restrict permissions to minimum required\n"
            f"4. Implement secret scanning in CI/CD pipeline\n"
            f"5. Use environment variables or secret managers instead of hardcoding"
        )


class BaseKeyValidator(ABC):
    """
    Abstract base class for API key validators.

    Each validator must implement:
    - validate(): Validate a key and return results
    - get_key_patterns(): Return regex patterns to detect this key type
    - get_key_type(): Return the KeyType this validator handles
    """

    def __init__(self, timeout: int = 30):
        """
        Initialize validator.

        Args:
            timeout: Request timeout in seconds
        """
        self.timeout = timeout

    @abstractmethod
    async def validate(self, key_info: APIKeyInfo) -> ValidationResult:
        """
        Validate the API key and return results.

        Args:
            key_info: Parsed key information

        Returns:
            ValidationResult with validation status and details
        """
        pass

    @abstractmethod
    def get_key_patterns(self) -> dict[str, re.Pattern]:
        """
        Return regex patterns to detect this key type.

        Returns:
            Dictionary of pattern_name -> compiled regex pattern
        """
        pass

    @abstractmethod
    def get_key_type(self) -> KeyType:
        """
        Return the primary KeyType this validator handles.

        Returns:
            KeyType enum value
        """
        pass

    def can_validate(self, key_info: APIKeyInfo) -> bool:
        """
        Check if this validator can handle the given key.

        Args:
            key_info: Key to check

        Returns:
            True if this validator can handle the key
        """
        return key_info.key_type == self.get_key_type()

    async def enumerate_permissions(self, key_info: APIKeyInfo) -> list[str]:
        """
        Enumerate permissions for the key (optional override).

        Args:
            key_info: Validated key

        Returns:
            List of permission strings
        """
        return []

    async def enumerate_resources(self, key_info: APIKeyInfo) -> list[str]:
        """
        Enumerate accessible resources (optional override).

        Args:
            key_info: Validated key

        Returns:
            List of resource identifiers
        """
        return []

    def assess_risk(self, result: ValidationResult) -> RiskLevel:
        """
        Assess risk level based on validation result.

        Args:
            result: Validation result

        Returns:
            Risk level assessment
        """
        if not result.is_valid:
            return RiskLevel.INFO

        # Check for critical permissions
        critical_keywords = [
            "admin", "root", "write", "delete", "create",
            "full", "all", "owner", "manage", "billing"
        ]

        perm_str = " ".join(result.permissions).lower()
        scope_str = " ".join(result.scopes).lower()
        combined = perm_str + " " + scope_str

        if any(kw in combined for kw in critical_keywords):
            return RiskLevel.CRITICAL

        if len(result.permissions) > 10 or len(result.resources_accessible) > 50:
            return RiskLevel.HIGH

        if result.permissions or result.resources_accessible:
            return RiskLevel.MEDIUM

        return RiskLevel.LOW

    def _create_error_result(
        self,
        key_info: APIKeyInfo,
        error: str,
        error_code: str = None,
        duration_ms: float = 0.0,
    ) -> ValidationResult:
        """
        Create a ValidationResult for an error case.

        Args:
            key_info: The key that was being validated
            error: Error message
            error_code: Optional error code
            duration_ms: Validation duration

        Returns:
            ValidationResult with error information
        """
        return ValidationResult(
            key_info=key_info,
            is_valid=False,
            error=error,
            error_code=error_code,
            validation_duration_ms=duration_ms,
            risk_level=RiskLevel.INFO,
        )
