"""
AIPTX Atlassian Key Validator
=============================

Validates Atlassian API tokens for Jira, Confluence, and Bitbucket.

Supports:
- API Tokens (used with email)
- OAuth 2.0 tokens
- Personal Access Tokens (Bitbucket)
"""

from __future__ import annotations

import re
import time
import logging
import base64
from typing import Any, Optional

import httpx

from ..base import (
    APIKeyInfo,
    ValidationResult,
    BaseKeyValidator,
    KeyType,
    RiskLevel,
)

logger = logging.getLogger(__name__)


class AtlassianKeyValidator(BaseKeyValidator):
    """
    Validate Atlassian API tokens and enumerate permissions.

    Uses Atlassian REST APIs to validate tokens and discover:
    - User identity
    - Accessible Jira projects
    - Accessible Confluence spaces
    - Bitbucket repositories
    """

    def get_key_type(self) -> KeyType:
        return KeyType.ATLASSIAN

    def get_key_patterns(self) -> dict[str, re.Pattern]:
        return {
            # Atlassian API tokens are typically base64-ish
            "atlassian_api_token": re.compile(r'[A-Za-z0-9]{24,}'),
            # Bitbucket app passwords
            "bitbucket_app_password": re.compile(r'[A-Za-z0-9]{20,}'),
        }

    async def validate(self, key_info: APIKeyInfo) -> ValidationResult:
        """
        Validate Atlassian token against API.

        Requires email + API token for cloud, or just token for server.
        """
        start_time = time.time()

        # Get site URL and email from metadata or defaults
        site_url = key_info.metadata.get("site_url", "")
        email = key_info.metadata.get("email") or key_info.key_id

        if not site_url:
            # Try to detect from key pattern or default to cloud
            site_url = "https://api.atlassian.com"

        if not email and not site_url.startswith("https://api.atlassian.com"):
            duration_ms = (time.time() - start_time) * 1000
            return self._create_error_result(
                key_info,
                error="Email required for Atlassian Cloud API tokens",
                error_code="MISSING_EMAIL",
                duration_ms=duration_ms,
            )

        try:
            async with httpx.AsyncClient(timeout=self.timeout) as client:
                # Build auth header
                if email:
                    auth_header = base64.b64encode(
                        f"{email}:{key_info.key_value}".encode()
                    ).decode()
                    headers = {
                        "Authorization": f"Basic {auth_header}",
                        "Accept": "application/json",
                    }
                else:
                    headers = {
                        "Authorization": f"Bearer {key_info.key_value}",
                        "Accept": "application/json",
                    }

                # Try to get user info
                result = await self._validate_cloud(client, key_info, headers, site_url, start_time)
                if result.is_valid or result.error_code != "ENDPOINT_NOT_FOUND":
                    return result

                # Try Jira Server/DC endpoint
                return await self._validate_server(client, key_info, headers, site_url, start_time)

        except httpx.TimeoutException:
            duration_ms = (time.time() - start_time) * 1000
            return self._create_error_result(
                key_info,
                error="Request timeout",
                error_code="TIMEOUT",
                duration_ms=duration_ms,
            )
        except Exception as e:
            duration_ms = (time.time() - start_time) * 1000
            logger.error(f"Atlassian validation error: {e}")
            return self._create_error_result(
                key_info,
                error=str(e),
                error_code="ERROR",
                duration_ms=duration_ms,
            )

    async def _validate_cloud(
        self,
        client: httpx.AsyncClient,
        key_info: APIKeyInfo,
        headers: dict,
        site_url: str,
        start_time: float,
    ) -> ValidationResult:
        """Validate against Atlassian Cloud APIs."""
        duration_ms = (time.time() - start_time) * 1000

        # First, get accessible resources
        response = await client.get(
            "https://api.atlassian.com/oauth/token/accessible-resources",
            headers=headers,
        )

        if response.status_code == 200:
            resources_data = response.json()

            if not resources_data:
                return self._create_error_result(
                    key_info,
                    error="No accessible Atlassian sites",
                    error_code="NO_RESOURCES",
                    duration_ms=duration_ms,
                )

            # Get the first site
            site = resources_data[0]
            cloud_id = site.get("id")
            site_name = site.get("name")
            site_url = site.get("url")

            # Get user info from Jira
            user_response = await client.get(
                f"https://api.atlassian.com/ex/jira/{cloud_id}/rest/api/3/myself",
                headers=headers,
            )

            if user_response.status_code == 200:
                user_data = user_response.json()

                identity_info = {
                    "account_id": user_data.get("accountId"),
                    "email": user_data.get("emailAddress"),
                    "display_name": user_data.get("displayName"),
                    "active": user_data.get("active", True),
                    "site_name": site_name,
                    "site_url": site_url,
                    "account_type": user_data.get("accountType"),
                }

                # Enumerate access
                projects, spaces, permissions = await self._enumerate_cloud_access(
                    client, headers, cloud_id
                )

                # Build resources list
                resources = []
                resources.extend([f"jira_project:{p}" for p in projects[:10]])
                resources.extend([f"confluence_space:{s}" for s in spaces[:10]])

                result = ValidationResult(
                    key_info=key_info,
                    is_valid=True,
                    validation_method="GET /myself",
                    validation_endpoint=f"https://api.atlassian.com/ex/jira/{cloud_id}/rest/api/3/myself",
                    identity_info=identity_info,
                    scopes=[],
                    permissions=permissions,
                    resources_accessible=resources,
                    raw_response=user_data,
                    validated_at=time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
                    validation_duration_ms=duration_ms,
                )

                result.risk_level = self._assess_atlassian_risk(projects, spaces, identity_info)
                result.exploitation_potential = self._get_exploitation_potential(result)
                result.attack_vectors = self._get_attack_vectors(projects, spaces, permissions)

                return result

        elif response.status_code == 401:
            return self._create_error_result(
                key_info,
                error="Invalid API token or email",
                error_code="401_UNAUTHORIZED",
                duration_ms=duration_ms,
            )

        return self._create_error_result(
            key_info,
            error="Could not access Atlassian Cloud",
            error_code="ENDPOINT_NOT_FOUND",
            duration_ms=duration_ms,
        )

    async def _validate_server(
        self,
        client: httpx.AsyncClient,
        key_info: APIKeyInfo,
        headers: dict,
        site_url: str,
        start_time: float,
    ) -> ValidationResult:
        """Validate against Jira Server/Data Center."""
        duration_ms = (time.time() - start_time) * 1000

        # Try Jira Server endpoint
        if not site_url.startswith("http"):
            site_url = f"https://{site_url}"

        response = await client.get(
            f"{site_url}/rest/api/2/myself",
            headers=headers,
        )

        if response.status_code == 200:
            user_data = response.json()

            identity_info = {
                "username": user_data.get("name"),
                "email": user_data.get("emailAddress"),
                "display_name": user_data.get("displayName"),
                "active": user_data.get("active", True),
                "site_url": site_url,
            }

            # Enumerate projects
            projects_response = await client.get(
                f"{site_url}/rest/api/2/project",
                headers=headers,
            )
            projects = []
            if projects_response.status_code == 200:
                project_data = projects_response.json()
                projects = [p.get("key") for p in project_data]

            result = ValidationResult(
                key_info=key_info,
                is_valid=True,
                validation_method="GET /rest/api/2/myself",
                validation_endpoint=f"{site_url}/rest/api/2/myself",
                identity_info=identity_info,
                scopes=[],
                permissions=["Jira access"],
                resources_accessible=[f"jira_project:{p}" for p in projects[:20]],
                raw_response=user_data,
                validated_at=time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
                validation_duration_ms=duration_ms,
            )

            result.risk_level = self._assess_atlassian_risk(projects, [], identity_info)
            result.exploitation_potential = self._get_exploitation_potential(result)
            result.attack_vectors = self._get_attack_vectors(projects, [], [])

            return result

        return self._create_error_result(
            key_info,
            error="Invalid credentials or server unreachable",
            error_code="401_UNAUTHORIZED",
            duration_ms=duration_ms,
        )

    async def _enumerate_cloud_access(
        self,
        client: httpx.AsyncClient,
        headers: dict,
        cloud_id: str,
    ) -> tuple[list[str], list[str], list[str]]:
        """Enumerate Jira projects and Confluence spaces."""
        projects = []
        spaces = []
        permissions = []

        # Get Jira projects
        try:
            response = await client.get(
                f"https://api.atlassian.com/ex/jira/{cloud_id}/rest/api/3/project",
                headers=headers,
            )
            if response.status_code == 200:
                data = response.json()
                projects = [p.get("key") for p in data]
                permissions.append("Jira project access")
        except Exception as e:
            logger.debug(f"Failed to enumerate Jira projects: {e}")

        # Get Confluence spaces
        try:
            response = await client.get(
                f"https://api.atlassian.com/ex/confluence/{cloud_id}/wiki/api/v2/spaces",
                headers=headers,
            )
            if response.status_code == 200:
                data = response.json()
                spaces = [s.get("key") for s in data.get("results", [])]
                permissions.append("Confluence space access")
        except Exception as e:
            logger.debug(f"Failed to enumerate Confluence spaces: {e}")

        # Check specific permissions
        try:
            response = await client.get(
                f"https://api.atlassian.com/ex/jira/{cloud_id}/rest/api/3/mypermissions",
                headers=headers,
            )
            if response.status_code == 200:
                data = response.json()
                for perm_name, perm_data in data.get("permissions", {}).items():
                    if perm_data.get("havePermission"):
                        permissions.append(perm_name)
        except Exception:
            pass

        return projects, spaces, permissions

    def _assess_atlassian_risk(
        self,
        projects: list[str],
        spaces: list[str],
        identity_info: dict,
    ) -> RiskLevel:
        """Assess risk level based on access."""
        total_resources = len(projects) + len(spaces)

        # Critical: Admin access or many resources
        if identity_info.get("account_type") == "atlassian" and total_resources > 20:
            return RiskLevel.CRITICAL

        # High: Access to many projects/spaces
        if total_resources > 10:
            return RiskLevel.HIGH

        # Medium: Access to some resources
        if total_resources > 0:
            return RiskLevel.MEDIUM

        # Low: Basic access
        return RiskLevel.LOW

    def _get_exploitation_potential(self, result: ValidationResult) -> str:
        """Get exploitation potential description."""
        if result.risk_level == RiskLevel.CRITICAL:
            return "Critical - Broad access to Jira/Confluence with admin potential"
        elif result.risk_level == RiskLevel.HIGH:
            return "High - Access to many projects and spaces"
        elif result.risk_level == RiskLevel.MEDIUM:
            return "Medium - Access to Jira projects and/or Confluence spaces"
        elif result.risk_level == RiskLevel.LOW:
            return "Low - Limited access to Atlassian resources"
        return "Minimal - Token has very limited permissions"

    def _get_attack_vectors(
        self,
        projects: list[str],
        spaces: list[str],
        permissions: list[str],
    ) -> list[str]:
        """Identify potential attack vectors."""
        vectors = []

        if projects:
            vectors.append("Access Jira issues (may contain sensitive data)")
            vectors.append("View project configurations and workflows")
            if any("CREATE" in p or "EDIT" in p for p in permissions):
                vectors.append("Create/modify issues for social engineering")

        if spaces:
            vectors.append("Access Confluence pages (documentation, secrets)")
            vectors.append("Search for sensitive information in wiki")
            if any("CREATE" in p or "EDIT" in p for p in permissions):
                vectors.append("Modify documentation for supply chain attacks")

        if "ADMINISTER" in permissions or "SYSTEM_ADMIN" in permissions:
            vectors.append("Administer Jira/Confluence settings")
            vectors.append("Create users or modify permissions")

        return vectors
