"""
ID Wispera Credential Locations Registry
Defines well-known credential storage paths for various providers
"""

import json
import os
import re
from pathlib import Path
from typing import Any, Callable, Optional

from pydantic import BaseModel, Field

from id_wispera.types import VisaType, CredentialType, RiskLevel


# ============================================================================
# Types
# ============================================================================

class CredentialClassification(BaseModel):
    """Credential classification from scan"""
    name: str
    credential_type: CredentialType = Field(alias="credentialType")
    visa_type: VisaType = Field(alias="visaType")
    risk_level: RiskLevel = Field(alias="riskLevel")
    platforms: list[str]
    tags: list[str]
    issuing_authority: str = Field(alias="issuingAuthority")
    context: Optional[dict[str, Any]] = None

    class Config:
        populate_by_name = True


class DiscoveredCredential(BaseModel):
    """A discovered credential from scanning"""
    provider: str
    file_path: str = Field(alias="filePath")
    classification: CredentialClassification
    raw_value: str = Field(alias="rawValue")
    metadata: dict[str, Any]
    expires_at: Optional[str] = Field(None, alias="expiresAt")
    insecure_permissions: Optional[bool] = Field(None, alias="insecurePermissions")

    class Config:
        populate_by_name = True


class LocationScanResult(BaseModel):
    """Result of scanning a location"""
    path: str
    exists: bool
    readable: bool
    error: Optional[str] = None
    credentials: list[DiscoveredCredential]
    permission_warning: Optional[str] = Field(None, alias="permissionWarning")

    class Config:
        populate_by_name = True


class ProviderLocation:
    """A location where credentials are stored"""

    def __init__(
        self,
        id: str,
        name: str,
        path_pattern: str,
        parse: Callable[[str, str], list[DiscoveredCredential]],
        file_pattern: Optional[str] = None,
    ):
        self.id = id
        self.name = name
        self.path_pattern = path_pattern
        self.file_pattern = file_pattern
        self.parse = parse


class ProviderDefinition:
    """Provider definition with credential locations"""

    def __init__(
        self,
        id: str,
        name: str,
        description: str,
        detect: Callable[[], bool],
        locations: list[ProviderLocation],
    ):
        self.id = id
        self.name = name
        self.description = description
        self.detect = detect
        self.locations = locations


# ============================================================================
# Path Helpers
# ============================================================================

def expand_path(pattern: str, vars: Optional[dict[str, str]] = None) -> str:
    """
    Expand path patterns like {home} to actual paths.

    Args:
        pattern: Path pattern with {home} and other variables
        vars: Additional variables to expand

    Returns:
        Expanded path string
    """
    result = pattern.replace("{home}", get_home_dir())
    if vars:
        for key, value in vars.items():
            result = result.replace(f"{{{key}}}", value)
    return result


def get_home_dir() -> str:
    """
    Get the home directory.

    Returns:
        Home directory path
    """
    return str(Path.home())


# ============================================================================
# Helper
# ============================================================================

def _capitalize_first(s: str) -> str:
    """Capitalize first letter of a string."""
    if not s:
        return s
    return s[0].upper() + s[1:]


# ============================================================================
# OpenClaw Provider
# ============================================================================

def _parse_whatsapp_creds(file_path: str, content: str) -> list[DiscoveredCredential]:
    """Parse WhatsApp credentials from creds.json."""
    try:
        data = json.loads(content)
        credentials: list[DiscoveredCredential] = []

        path_match = re.search(r"whatsapp/([^/]+)/creds\.json$", file_path)
        me_id = data.get("me", {}).get("id", "")
        account_id = (path_match.group(1) if path_match
                      else me_id.split(":")[0] if me_id else "unknown")
        phone_number = me_id.split(":")[0] if me_id else account_id

        credentials.append(DiscoveredCredential(
            provider="openclaw",
            file_path=file_path,
            classification=CredentialClassification(
                name=f"WhatsApp Session ({account_id})",
                credential_type=CredentialType.SESSION_KEYS,
                visa_type=VisaType.ACCESS,
                risk_level=RiskLevel.HIGH,
                platforms=["openclaw", "whatsapp"],
                tags=["openclaw", "whatsapp", "session", "imported"],
                issuing_authority="OpenClaw (self-managed)",
            ),
            raw_value=content,
            metadata={
                "accountId": account_id,
                "phoneNumber": phone_number,
                "botName": data.get("me", {}).get("name"),
                "registrationId": data.get("registrationId"),
                "hasNoiseKey": bool(data.get("noiseKey")),
                "hasSignedIdentityKey": bool(data.get("signedIdentityKey")),
                "hasSignedPreKey": bool(data.get("signedPreKey")),
            },
        ))

        return credentials
    except Exception:
        return []


def _parse_allowlist(file_path: str, content: str) -> list[DiscoveredCredential]:
    """Parse channel allowlist files."""
    try:
        data = json.loads(content)
        credentials: list[DiscoveredCredential] = []

        file_match = re.search(r"([^/]+)-allowFrom\.json$", file_path)
        channel = file_match.group(1) if file_match else "unknown"
        paired_count = len(data)

        credentials.append(DiscoveredCredential(
            provider="openclaw",
            file_path=file_path,
            classification=CredentialClassification(
                name=f"Pairing Allowlist: {channel} ({paired_count} users)",
                credential_type=CredentialType.CUSTOM,
                visa_type=VisaType.ACCESS,
                risk_level=RiskLevel.LOW,
                platforms=["openclaw", channel],
                tags=["openclaw", channel, "allowlist", "imported"],
                issuing_authority="OpenClaw (self-managed)",
            ),
            raw_value=content,
            metadata={
                "channel": channel,
                "pairedUsers": [
                    {"id": uid, "name": info.get("name"), "pairedAt": info.get("pairedAt")}
                    for uid, info in data.items()
                    if isinstance(info, dict)
                ],
                "pairedCount": paired_count,
            },
        ))

        return credentials
    except Exception:
        return []


def _parse_auth_profiles(file_path: str, content: str) -> list[DiscoveredCredential]:
    """Parse auth-profiles.json for LLM API keys."""
    try:
        data = json.loads(content)
        credentials: list[DiscoveredCredential] = []

        path_match = re.search(r"agents/([^/]+)/agent/auth-profiles\.json$", file_path)
        agent_id = path_match.group(1) if path_match else "unknown"

        for provider_name, profile in data.items():
            if isinstance(profile, dict) and profile.get("type") == "api-key" and profile.get("key"):
                key = profile["key"]
                key_preview = key[:10] + "..." + key[-4:]

                credentials.append(DiscoveredCredential(
                    provider="openclaw",
                    file_path=file_path,
                    classification=CredentialClassification(
                        name=f"{_capitalize_first(provider_name)} API Key ({key_preview})",
                        credential_type=CredentialType.API_KEY,
                        visa_type=VisaType.PRIVILEGE,
                        risk_level=RiskLevel.CRITICAL,
                        platforms=["openclaw", "mcp", provider_name],
                        tags=["openclaw", provider_name, "api-key", "llm", "imported"],
                        issuing_authority="OpenClaw (self-managed)",
                        context={"agentId": agent_id, "model": profile.get("model")},
                    ),
                    raw_value=key,
                    metadata={
                        "agentId": agent_id,
                        "provider": provider_name,
                        "model": profile.get("model"),
                        "keyType": profile.get("type"),
                    },
                ))

        return credentials
    except Exception:
        return []


def _parse_oauth_tokens(file_path: str, content: str) -> list[DiscoveredCredential]:
    """Parse oauth.json for OAuth tokens."""
    try:
        data = json.loads(content)
        credentials: list[DiscoveredCredential] = []

        for provider_name, tokens in data.items():
            if isinstance(tokens, dict) and tokens.get("access_token"):
                token_preview = tokens["access_token"][:10] + "..."
                expires_at_epoch = tokens.get("expires_at")
                expires_at = None
                is_expired = False
                if expires_at_epoch:
                    from datetime import datetime
                    expires_at_dt = datetime.utcfromtimestamp(expires_at_epoch)
                    expires_at = expires_at_dt.isoformat()
                    is_expired = expires_at_dt < datetime.utcnow()

                tags = ["openclaw", provider_name, "oauth", "imported"]
                if is_expired:
                    tags.append("expired")

                credentials.append(DiscoveredCredential(
                    provider="openclaw",
                    file_path=file_path,
                    classification=CredentialClassification(
                        name=f"{_capitalize_first(provider_name)} OAuth Token ({token_preview})",
                        credential_type=CredentialType.OAUTH_TOKEN,
                        visa_type=VisaType.ACCESS,
                        risk_level=RiskLevel.MEDIUM,
                        platforms=["openclaw", provider_name],
                        tags=tags,
                        issuing_authority="OpenClaw (self-managed)",
                    ),
                    raw_value=tokens["access_token"],
                    metadata={
                        "provider": provider_name,
                        "hasRefreshToken": bool(tokens.get("refresh_token")),
                        "tokenType": tokens.get("token_type"),
                        "expiresAt": expires_at,
                        "isExpired": is_expired,
                    },
                    expires_at=expires_at,
                ))

        return credentials
    except Exception:
        return []


def _parse_openclaw_config(file_path: str, content: str) -> list[DiscoveredCredential]:
    """Parse openclaw.json for channel tokens and gateway config."""
    try:
        data = json.loads(content)
        credentials: list[DiscoveredCredential] = []

        # Parse channel tokens
        channels = data.get("channels", {})
        if isinstance(channels, dict):
            for channel, config in channels.items():
                if not isinstance(config, dict):
                    continue

                # Telegram bot token
                if channel == "telegram" and config.get("token"):
                    token = config["token"]
                    token_preview = token[:7] + "..." + token[-4:]
                    credentials.append(DiscoveredCredential(
                        provider="openclaw",
                        file_path=file_path,
                        classification=CredentialClassification(
                            name=f"Telegram Bot Token ({token_preview})",
                            credential_type=CredentialType.API_KEY,
                            visa_type=VisaType.ACCESS,
                            risk_level=RiskLevel.HIGH,
                            platforms=["openclaw", "telegram"],
                            tags=["openclaw", "telegram", "bot-token", "imported"],
                            issuing_authority="OpenClaw (self-managed)",
                        ),
                        raw_value=token,
                        metadata={"channel": "telegram"},
                    ))

                # Slack tokens
                if channel == "slack":
                    if config.get("botToken"):
                        token = config["botToken"]
                        token_preview = token[:10] + "..." + token[-4:]
                        credentials.append(DiscoveredCredential(
                            provider="openclaw",
                            file_path=file_path,
                            classification=CredentialClassification(
                                name=f"Slack Bot Token ({token_preview})",
                                credential_type=CredentialType.API_KEY,
                                visa_type=VisaType.ACCESS,
                                risk_level=RiskLevel.HIGH,
                                platforms=["openclaw", "slack"],
                                tags=["openclaw", "slack", "bot-token", "imported"],
                                issuing_authority="OpenClaw (self-managed)",
                            ),
                            raw_value=token,
                            metadata={"channel": "slack", "tokenType": "bot"},
                        ))
                    if config.get("appToken"):
                        token = config["appToken"]
                        token_preview = token[:10] + "..." + token[-4:]
                        credentials.append(DiscoveredCredential(
                            provider="openclaw",
                            file_path=file_path,
                            classification=CredentialClassification(
                                name=f"Slack App Token ({token_preview})",
                                credential_type=CredentialType.API_KEY,
                                visa_type=VisaType.ACCESS,
                                risk_level=RiskLevel.HIGH,
                                platforms=["openclaw", "slack"],
                                tags=["openclaw", "slack", "app-token", "imported"],
                                issuing_authority="OpenClaw (self-managed)",
                            ),
                            raw_value=token,
                            metadata={"channel": "slack", "tokenType": "app"},
                        ))

                # Discord token
                if channel == "discord" and config.get("token"):
                    token = config["token"]
                    token_preview = token[:10] + "..." + token[-4:]
                    credentials.append(DiscoveredCredential(
                        provider="openclaw",
                        file_path=file_path,
                        classification=CredentialClassification(
                            name=f"Discord Bot Token ({token_preview})",
                            credential_type=CredentialType.API_KEY,
                            visa_type=VisaType.ACCESS,
                            risk_level=RiskLevel.HIGH,
                            platforms=["openclaw", "discord"],
                            tags=["openclaw", "discord", "bot-token", "imported"],
                            issuing_authority="OpenClaw (self-managed)",
                        ),
                        raw_value=token,
                        metadata={"channel": "discord"},
                    ))

        # Parse gateway token
        gateway = data.get("gateway", {})
        if isinstance(gateway, dict) and gateway.get("token"):
            token = gateway["token"]
            token_preview = token[:6] + "..." + token[-4:]
            credentials.append(DiscoveredCredential(
                provider="openclaw",
                file_path=file_path,
                classification=CredentialClassification(
                    name=f"Gateway Token ({token_preview})",
                    credential_type=CredentialType.API_KEY,
                    visa_type=VisaType.PRIVILEGE,
                    risk_level=RiskLevel.HIGH,
                    platforms=["openclaw"],
                    tags=["openclaw", "gateway", "admin", "imported"],
                    issuing_authority="OpenClaw (self-managed)",
                ),
                raw_value=token,
                metadata={"gatewayPort": gateway.get("port")},
            ))

        return credentials
    except Exception:
        return []


# ============================================================================
# AWS Provider
# ============================================================================

def _parse_aws_credentials(file_path: str, content: str) -> list[DiscoveredCredential]:
    """Parse AWS credentials file."""
    credentials: list[DiscoveredCredential] = []
    profile_regex = re.compile(r"\[([^\]]+)\]\s*\n((?:[^\[]*(?:\n|$))*)", re.MULTILINE)

    for match in profile_regex.finditer(content):
        profile_name = match.group(1) or "default"
        profile_content = match.group(2) or ""

        access_key_match = re.search(r"aws_access_key_id\s*=\s*(\S+)", profile_content)
        secret_key_match = re.search(r"aws_secret_access_key\s*=\s*(\S+)", profile_content)

        if access_key_match and secret_key_match:
            access_key = access_key_match.group(1)
            secret_key = secret_key_match.group(1)
            key_preview = access_key[:8] + "..."
            credentials.append(DiscoveredCredential(
                provider="aws",
                file_path=file_path,
                classification=CredentialClassification(
                    name=f"AWS Access Key [{profile_name}] ({key_preview})",
                    credential_type=CredentialType.API_KEY,
                    visa_type=VisaType.PRIVILEGE,
                    risk_level=RiskLevel.CRITICAL,
                    platforms=["aws"],
                    tags=["aws", profile_name, "imported"],
                    issuing_authority="AWS IAM",
                ),
                raw_value=f"{access_key}:{secret_key}",
                metadata={
                    "profile": profile_name,
                    "accessKeyId": access_key,
                },
            ))

    return credentials


# ============================================================================
# SSH Provider
# ============================================================================

def _parse_ssh_keys(file_path: str, content: str) -> list[DiscoveredCredential]:
    """Parse SSH private keys."""
    credentials: list[DiscoveredCredential] = []

    if file_path.endswith(".pub"):
        return credentials

    if "PRIVATE KEY" in content:
        if "RSA" in content:
            key_type = "RSA"
        elif "EC" in content:
            key_type = "EC"
        elif "OPENSSH" in content:
            key_type = "OpenSSH"
        else:
            key_type = "Unknown"

        file_name = os.path.basename(file_path)

        credentials.append(DiscoveredCredential(
            provider="ssh",
            file_path=file_path,
            classification=CredentialClassification(
                name=f"SSH Private Key ({file_name})",
                credential_type=CredentialType.PRIVATE_KEY,
                visa_type=VisaType.ACCESS,
                risk_level=RiskLevel.HIGH,
                platforms=["ssh"],
                tags=["ssh", key_type.lower(), "imported"],
                issuing_authority="Self-generated",
            ),
            raw_value=content,
            metadata={
                "keyType": key_type,
                "fileName": file_name,
            },
        ))

    return credentials


# ============================================================================
# Docker Provider
# ============================================================================

def _parse_docker_config(file_path: str, content: str) -> list[DiscoveredCredential]:
    """Parse Docker config.json for registry credentials."""
    credentials: list[DiscoveredCredential] = []

    try:
        data = json.loads(content)
        auths = data.get("auths", {})
        if isinstance(auths, dict):
            for registry, auth in auths.items():
                if isinstance(auth, dict) and auth.get("auth"):
                    credentials.append(DiscoveredCredential(
                        provider="docker",
                        file_path=file_path,
                        classification=CredentialClassification(
                            name=f"Docker Registry ({registry})",
                            credential_type=CredentialType.API_KEY,
                            visa_type=VisaType.ACCESS,
                            risk_level=RiskLevel.MEDIUM,
                            platforms=["docker"],
                            tags=["docker", "registry", "imported"],
                            issuing_authority=registry,
                        ),
                        raw_value=auth["auth"],
                        metadata={"registry": registry},
                    ))
    except Exception:
        pass

    return credentials


# ============================================================================
# npm Provider
# ============================================================================

def _parse_npmrc(file_path: str, content: str) -> list[DiscoveredCredential]:
    """Parse .npmrc for auth tokens."""
    credentials: list[DiscoveredCredential] = []

    token_match = re.search(r"_authToken=([^\s\n]+)", content)
    if token_match:
        token = token_match.group(1)
        token_preview = token[:8] + "..."
        credentials.append(DiscoveredCredential(
            provider="npm",
            file_path=file_path,
            classification=CredentialClassification(
                name=f"npm Auth Token ({token_preview})",
                credential_type=CredentialType.API_KEY,
                visa_type=VisaType.ACCESS,
                risk_level=RiskLevel.MEDIUM,
                platforms=["npm"],
                tags=["npm", "registry", "imported"],
                issuing_authority="npm Registry",
            ),
            raw_value=token,
            metadata={},
        ))

    return credentials


# ============================================================================
# Kubernetes Provider
# ============================================================================

def _parse_kubeconfig(file_path: str, content: str) -> list[DiscoveredCredential]:
    """Parse kubeconfig for cluster credentials."""
    credentials: list[DiscoveredCredential] = []

    cluster_matches = re.findall(r"- name:\s*([^\n]+)", content)
    if cluster_matches:
        name = cluster_matches[0].strip()
        credentials.append(DiscoveredCredential(
            provider="kubernetes",
            file_path=file_path,
            classification=CredentialClassification(
                name=f"Kubernetes Cluster ({name})",
                credential_type=CredentialType.CUSTOM,
                visa_type=VisaType.PRIVILEGE,
                risk_level=RiskLevel.HIGH,
                platforms=["kubernetes"],
                tags=["kubernetes", "cluster", "imported"],
                issuing_authority="Kubernetes",
            ),
            raw_value=content,
            metadata={"clusterName": name},
        ))

    return credentials


# ============================================================================
# Provider Definitions
# ============================================================================

def _detect_path_exists(path: str) -> bool:
    """Check if a path exists."""
    return os.path.exists(path)


openclaw_provider = ProviderDefinition(
    id="openclaw",
    name="OpenClaw",
    description="OpenClaw AI agent framework credentials",
    detect=lambda: _detect_path_exists(os.path.join(get_home_dir(), ".openclaw")),
    locations=[
        ProviderLocation(
            id="whatsapp-creds",
            name="WhatsApp Credentials",
            path_pattern="{home}/.openclaw/credentials/whatsapp",
            file_pattern="**/creds.json",
            parse=_parse_whatsapp_creds,
        ),
        ProviderLocation(
            id="channel-allowlists",
            name="Channel Allowlists",
            path_pattern="{home}/.openclaw/credentials",
            file_pattern="*-allowFrom.json",
            parse=_parse_allowlist,
        ),
        ProviderLocation(
            id="auth-profiles",
            name="LLM Auth Profiles",
            path_pattern="{home}/.openclaw/agents",
            file_pattern="**/agent/auth-profiles.json",
            parse=_parse_auth_profiles,
        ),
        ProviderLocation(
            id="oauth-tokens",
            name="OAuth Tokens",
            path_pattern="{home}/.openclaw/credentials",
            file_pattern="oauth.json",
            parse=_parse_oauth_tokens,
        ),
        ProviderLocation(
            id="config",
            name="OpenClaw Config",
            path_pattern="{home}/.openclaw",
            file_pattern="openclaw.json",
            parse=_parse_openclaw_config,
        ),
    ],
)

aws_provider = ProviderDefinition(
    id="aws",
    name="AWS",
    description="Amazon Web Services credentials",
    detect=lambda: _detect_path_exists(os.path.join(get_home_dir(), ".aws")),
    locations=[
        ProviderLocation(
            id="credentials",
            name="AWS Credentials",
            path_pattern="{home}/.aws",
            file_pattern="credentials",
            parse=_parse_aws_credentials,
        ),
    ],
)

ssh_provider = ProviderDefinition(
    id="ssh",
    name="SSH",
    description="SSH private keys",
    detect=lambda: _detect_path_exists(os.path.join(get_home_dir(), ".ssh")),
    locations=[
        ProviderLocation(
            id="private-keys",
            name="SSH Private Keys",
            path_pattern="{home}/.ssh",
            file_pattern="id_*",
            parse=_parse_ssh_keys,
        ),
    ],
)

docker_provider = ProviderDefinition(
    id="docker",
    name="Docker",
    description="Docker registry credentials",
    detect=lambda: _detect_path_exists(
        os.path.join(get_home_dir(), ".docker", "config.json")
    ),
    locations=[
        ProviderLocation(
            id="config",
            name="Docker Config",
            path_pattern="{home}/.docker",
            file_pattern="config.json",
            parse=_parse_docker_config,
        ),
    ],
)

npm_provider = ProviderDefinition(
    id="npm",
    name="npm",
    description="npm registry tokens",
    detect=lambda: _detect_path_exists(os.path.join(get_home_dir(), ".npmrc")),
    locations=[
        ProviderLocation(
            id="npmrc",
            name="npm Config",
            path_pattern="{home}",
            file_pattern=".npmrc",
            parse=_parse_npmrc,
        ),
    ],
)

kubernetes_provider = ProviderDefinition(
    id="kubernetes",
    name="Kubernetes",
    description="Kubernetes cluster credentials",
    detect=lambda: _detect_path_exists(
        os.path.join(get_home_dir(), ".kube", "config")
    ),
    locations=[
        ProviderLocation(
            id="kubeconfig",
            name="Kubeconfig",
            path_pattern="{home}/.kube",
            file_pattern="config",
            parse=_parse_kubeconfig,
        ),
    ],
)


# ============================================================================
# Provider Registry
# ============================================================================

providers: list[ProviderDefinition] = [
    openclaw_provider,
    aws_provider,
    ssh_provider,
    docker_provider,
    npm_provider,
    kubernetes_provider,
]


def get_provider(id: str) -> Optional[ProviderDefinition]:
    """
    Get provider by ID.

    Args:
        id: Provider identifier

    Returns:
        ProviderDefinition or None if not found
    """
    for p in providers:
        if p.id == id:
            return p
    return None


def get_provider_ids() -> list[str]:
    """
    Get all provider IDs.

    Returns:
        List of provider ID strings
    """
    return [p.id for p in providers]


def detect_installed_providers() -> list[ProviderDefinition]:
    """
    Detect which providers are installed.

    Returns:
        List of installed ProviderDefinition objects
    """
    installed: list[ProviderDefinition] = []
    for provider in providers:
        try:
            if provider.detect():
                installed.append(provider)
        except Exception:
            pass
    return installed


# ============================================================================
# Risk Level Helpers
# ============================================================================

def get_risk_emoji(level: str) -> str:
    """
    Get risk level emoji.

    Args:
        level: Risk level string

    Returns:
        Emoji string for the risk level
    """
    level_val = level.value if hasattr(level, "value") else level
    return {
        "critical": "\U0001f534",  # red circle
        "high": "\u26a0\ufe0f ",   # warning sign
        "medium": "\u26a0\ufe0f ", # warning sign
        "low": "\u2139\ufe0f ",    # info
    }.get(level_val, "")


def get_risk_label(level: str) -> str:
    """
    Get risk level label.

    Args:
        level: Risk level string

    Returns:
        Uppercase label
    """
    level_val = level.value if hasattr(level, "value") else level
    return level_val.upper()


def sort_by_risk(credentials: list[DiscoveredCredential]) -> list[DiscoveredCredential]:
    """
    Sort credentials by risk level (critical first).

    Args:
        credentials: List of discovered credentials

    Returns:
        Sorted list of credentials
    """
    order = {"critical": 0, "high": 1, "medium": 2, "low": 3}

    def risk_key(cred: DiscoveredCredential) -> int:
        risk = cred.classification.risk_level
        risk_val = risk.value if hasattr(risk, "value") else risk
        return order.get(risk_val, 4)

    return sorted(credentials, key=risk_key)
