# Copyright (c) 2026 MergeGuide, Inc. All rights reserved.
# SPDX-License-Identifier: Proprietary
"""
Vanta GRC Platform Adapter

Integrates with Vanta's API for SOC 2 compliance automation.
API Documentation: https://developer.vanta.com/docs
"""

from datetime import datetime, timedelta
from typing import Any

import httpx

from .base import (
    AuthToken,
    EvidenceType,
    GRCAdapter,
    GRCConfig,
    GRCEvidence,
    GRCPlatform,
    safe_raise_for_status,
    validate_resource_id,
)


class VantaAdapter(GRCAdapter):
    """
    Vanta platform adapter.

    Supports:
    - OAuth 2.0 authentication
    - Custom resource creation
    - Document upload
    - Control listing

    Rate Limits: Tiered (5-250 requests/minute by endpoint)
    """

    def __init__(self, config: GRCConfig):
        super().__init__(config)
        self._client = httpx.AsyncClient(timeout=30.0)

    @property
    def platform(self) -> GRCPlatform:
        return GRCPlatform.VANTA

    @property
    def base_url(self) -> str:
        return "https://api.vanta.com"

    async def authenticate(self) -> AuthToken:
        """
        Authenticate with Vanta using OAuth 2.0 client credentials.
        """
        response = await self._request_with_retry(
            self._client,
            "POST",
            f"{self.base_url}/oauth/token",
            data={
                "grant_type": "client_credentials",
                "client_id": self.config.client_id,
                "client_secret": self.config.client_secret,
                "scope": "vanta-api.all:read vanta-api.all:write",
            },
        )
        safe_raise_for_status(response, "Vanta authentication")
        data = response.json()

        expires_at = datetime.utcnow() + timedelta(seconds=data.get("expires_in", 3600))

        return AuthToken(
            access_token=data["access_token"],
            token_type=data.get("token_type", "Bearer"),
            expires_in=data.get("expires_in", 3600),
            refresh_token=data.get("refresh_token"),
            scope=data.get("scope"),
            expires_at=expires_at,
        )

    async def refresh_token(self) -> AuthToken:
        """
        Refresh Vanta authentication token.
        """
        if not self.config.refresh_token:
            return await self.authenticate()

        response = await self._request_with_retry(
            self._client,
            "POST",
            f"{self.base_url}/oauth/token",
            data={
                "grant_type": "refresh_token",
                "client_id": self.config.client_id,
                "client_secret": self.config.client_secret,
                "refresh_token": self.config.refresh_token,
            },
        )
        safe_raise_for_status(response, "Vanta token refresh")
        data = response.json()

        expires_at = datetime.utcnow() + timedelta(seconds=data.get("expires_in", 3600))

        return AuthToken(
            access_token=data["access_token"],
            token_type=data.get("token_type", "Bearer"),
            expires_in=data.get("expires_in", 3600),
            refresh_token=data.get("refresh_token", self.config.refresh_token),
            expires_at=expires_at,
        )

    async def _get_headers(self) -> dict[str, str]:
        """Get headers with authentication token."""
        token = await self.ensure_authenticated()
        return {
            "Authorization": f"Bearer {token}",
            "Content-Type": "application/json",
        }

    async def upload_evidence(self, evidence: GRCEvidence, control_id: str) -> dict[str, Any]:
        """
        Upload evidence to Vanta as a document linked to controls.
        """
        headers = await self._get_headers()

        # Transform evidence to Vanta document format
        payload = self._transform_to_vanta_document(evidence, control_id)

        response = await self._request_with_retry(
            self._client,
            "POST",
            f"{self.base_url}/v1/documents",
            headers=headers,
            json=payload,
        )
        response.raise_for_status()

        return {
            "success": True,
            "document_id": response.json().get("id"),
            "control_id": control_id,
            "platform": self.platform.value,
        }

    async def create_custom_resource(
        self,
        resource_type: str,
        display_name: str,
        data: dict[str, Any],
    ) -> dict[str, Any]:
        """
        Create a custom resource in Vanta.

        Custom resources allow storing MergeGuide evaluation data.
        """
        headers = await self._get_headers()

        payload = {
            "resourceType": resource_type,
            "displayName": display_name,
            "data": data,
        }

        response = await self._request_with_retry(
            self._client,
            "POST",
            f"{self.base_url}/v1/resources",
            headers=headers,
            json=payload,
        )
        response.raise_for_status()

        return response.json()

    async def create_evaluation_resource(self, evidence: GRCEvidence) -> dict[str, Any]:
        """
        Create a MergeGuide evaluation as a Vanta custom resource.
        """
        data = {
            "evaluationId": evidence.evaluation_id,
            "repository": evidence.repository,
            "policyId": evidence.policy_id,
            "policyName": evidence.policy_name,
            "overallStatus": "PASS" if evidence.passed else "FAIL",
            "violationCount": evidence.violation_count,
            "timestamp": evidence.collected_at.isoformat() + "Z",
            "metadata": evidence.metadata,
        }

        return await self.create_custom_resource(
            resource_type="MergeGuideEvaluation",
            display_name=f"MergeGuide: {evidence.repository}",
            data=data,
        )

    async def list_controls(self, framework: str | None = None) -> list[dict]:
        """
        List all controls from Vanta.

        Args:
            framework: Optional framework filter (e.g., "SOC 2", "ISO 27001")
        """
        headers = await self._get_headers()

        params = {"pageSize": 100}
        if framework:
            params["framework"] = framework

        controls = []
        next_cursor = None
        max_pages = 100  # Prevent infinite loop on malformed API responses

        for _page in range(max_pages):
            if next_cursor:
                params["cursor"] = next_cursor

            response = await self._request_with_retry(
                self._client,
                "GET",
                f"{self.base_url}/v1/controls",
                headers=headers,
                params=params,
            )
            response.raise_for_status()

            data = response.json()
            controls.extend(data.get("results", []))

            next_cursor = data.get("pageInfo", {}).get("endCursor")
            if not next_cursor or not data.get("pageInfo", {}).get("hasNextPage"):
                break

        return [
            {
                "id": c.get("id"),
                "name": c.get("name"),
                "description": c.get("description"),
                "framework": c.get("frameworkName"),
                "framework_control": c.get("controlIdentifier"),
                "status": c.get("status"),
                "category": c.get("category"),
            }
            for c in controls
        ]

    async def get_control(self, control_id: str) -> dict:
        """
        Get details for a specific Vanta control.
        """
        validate_resource_id(control_id, "control_id")
        headers = await self._get_headers()

        response = await self._request_with_retry(
            self._client,
            "GET",
            f"{self.base_url}/v1/controls/{control_id}",
            headers=headers,
        )
        response.raise_for_status()

        c = response.json()
        return {
            "id": c.get("id"),
            "name": c.get("name"),
            "description": c.get("description"),
            "framework": c.get("frameworkName"),
            "framework_control": c.get("controlIdentifier"),
            "status": c.get("status"),
            "category": c.get("category"),
            "tests": c.get("tests", []),
            "document_count": c.get("documentCount", 0),
        }

    async def update_control_status(
        self,
        control_id: str,
        status: str,
        evidence_ids: list[str] | None = None,
    ) -> dict:
        """
        Link documents to a control in Vanta.

        Note: In Vanta, control status is determined by linked evidence
        and automated tests, not directly set.
        """
        validate_resource_id(control_id, "control_id")
        headers = await self._get_headers()

        # Link documents to control
        if evidence_ids:
            for doc_id in evidence_ids:
                validate_resource_id(doc_id, "document_id")
                await self._request_with_retry(
                    self._client,
                    "POST",
                    f"{self.base_url}/v1/controls/{control_id}/documents",
                    headers=headers,
                    json={"documentId": doc_id},
                )

        return {
            "success": True,
            "control_id": control_id,
            "status": status,
            "linked_documents": evidence_ids or [],
        }

    async def list_frameworks(self) -> list[dict]:
        """
        List available compliance frameworks in Vanta.
        """
        headers = await self._get_headers()

        response = await self._request_with_retry(
            self._client,
            "GET",
            f"{self.base_url}/v1/frameworks",
            headers=headers,
        )
        response.raise_for_status()

        frameworks = response.json().get("results", [])

        return [
            {
                "id": f.get("id"),
                "name": f.get("name"),
                "description": f.get("description"),
                "control_count": f.get("controlCount"),
            }
            for f in frameworks
        ]

    async def register_custom_integration(
        self,
        name: str,
        description: str,
        resource_schema: dict,
    ) -> dict:
        """
        Register a custom integration with Vanta.

        This creates the MergeGuideEvaluation resource type.
        """
        headers = await self._get_headers()

        payload = {
            "name": name,
            "description": description,
            "resourceType": "MergeGuideEvaluation",
            "schema": resource_schema,
        }

        response = await self._request_with_retry(
            self._client,
            "POST",
            f"{self.base_url}/v1/integrations/custom",
            headers=headers,
            json=payload,
        )
        response.raise_for_status()

        return response.json()

    def _transform_to_vanta_document(self, evidence: GRCEvidence, control_id: str) -> dict:
        """
        Transform MergeGuide evidence to Vanta's document format.
        """
        # Determine document type
        if evidence.evidence_type == EvidenceType.FILE:
            doc_type = "policy_evidence"
        else:
            doc_type = "other"

        payload = {
            "documentType": doc_type,
            "fileName": evidence.file_name or f"mergeguide-evidence-{evidence.evidence_id}.json",
            "content": evidence.content,  # Base64 encoded
            "controlIds": [control_id] if control_id else evidence.control_ids,
            "metadata": {
                "source": "MergeGuide",
                "evaluationId": evidence.evaluation_id,
                "repository": evidence.repository,
                "policyId": evidence.policy_id,
                "policyName": evidence.policy_name,
                "version": "0.4.0",
            },
        }

        return payload

    async def close(self):
        """Close the HTTP client."""
        await self._client.aclose()
