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

Integrates with Hyperproof's API for compliance operations.
API Documentation: https://developer.hyperproof.app/
"""

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 HyperproofAdapter(GRCAdapter):
    """
    Hyperproof platform adapter.

    Supports:
    - OAuth 2.0 authentication (client credentials flow)
    - Proof upload
    - Control listing and management
    - Label-based organization

    Hyperproof has a simpler API structure, making integration straightforward.
    """

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

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

    @property
    def base_url(self) -> str:
        return "https://api.hyperproof.app/v1"

    async def authenticate(self) -> AuthToken:
        """
        Authenticate with Hyperproof using OAuth 2.0 client credentials.

        Hyperproof supports the client credentials flow for automation.
        """
        response = await self._request_with_retry(
            self._client,
            "POST",
            "https://accounts.hyperproof.app/oauth/token",
            data={
                "grant_type": "client_credentials",
                "client_id": self.config.client_id,
                "client_secret": self.config.client_secret,
            },
        )
        safe_raise_for_status(response, "Hyperproof 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 Hyperproof authentication token.
        """
        if not self.config.refresh_token:
            return await self.authenticate()

        response = await self._request_with_retry(
            self._client,
            "POST",
            "https://accounts.hyperproof.app/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, "Hyperproof 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 proof to a Hyperproof control.

        Hyperproof uses "proof" terminology for compliance evidence.
        """
        validate_resource_id(control_id, "control_id")
        headers = await self._get_headers()

        # Transform evidence to Hyperproof proof format
        payload = self._transform_to_hyperproof_proof(evidence, control_id)

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

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

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

        Args:
            framework: Optional framework/program filter
        """
        headers = await self._get_headers()

        params = {}
        if framework:
            params["programId"] = framework

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

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

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

    async def get_control(self, control_id: str) -> dict:
        """
        Get details for a specific Hyperproof 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}/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("programName"),
            "framework_control": c.get("identifier"),
            "status": c.get("status"),
            "category": c.get("domain"),
            "proof_count": c.get("proofCount", 0),
            "labels": c.get("labels", []),
        }

    async def update_control_status(
        self,
        control_id: str,
        status: str,
        evidence_ids: list[str] | None = None,
    ) -> dict:
        """
        Update a control's status in Hyperproof.
        """
        validate_resource_id(control_id, "control_id")
        headers = await self._get_headers()

        payload = {
            "status": status,
        }

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

        # Link proof if provided
        if evidence_ids:
            for proof_id in evidence_ids:
                validate_resource_id(proof_id, "proof_id")
                await self._request_with_retry(
                    self._client,
                    "POST",
                    f"{self.base_url}/controls/{control_id}/proof/{proof_id}/link",
                    headers=headers,
                )

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

    async def list_programs(self) -> list[dict]:
        """
        List compliance programs (frameworks) in Hyperproof.
        """
        headers = await self._get_headers()

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

        programs = response.json().get("data", [])

        return [
            {
                "id": p.get("id"),
                "name": p.get("name"),
                "description": p.get("description"),
                "control_count": p.get("controlCount", 0),
            }
            for p in programs
        ]

    async def create_label(self, name: str, color: str = "#4A90D9") -> dict:
        """
        Create a label for organizing proof.
        """
        headers = await self._get_headers()

        payload = {
            "name": name,
            "color": color,
        }

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

        return response.json()

    async def list_labels(self) -> list[dict]:
        """
        List all labels in Hyperproof.
        """
        headers = await self._get_headers()

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

        return response.json().get("data", [])

    async def get_proof(self, proof_id: str) -> dict:
        """
        Get a specific proof item.
        """
        validate_resource_id(proof_id, "proof_id")
        headers = await self._get_headers()

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

        return response.json()

    def _transform_to_hyperproof_proof(self, evidence: GRCEvidence, control_id: str) -> dict:
        """
        Transform MergeGuide evidence to Hyperproof's proof format.
        """
        # Determine proof type
        proof_type = "FILE" if evidence.evidence_type == EvidenceType.FILE else "TEXT"

        payload = {
            "controlId": control_id,
            "proofType": proof_type,
            "description": f"{evidence.title}: {evidence.description}",
            "collectedDate": evidence.collected_at.strftime("%Y-%m-%d"),
            "labels": ["MergeGuide", "Automated"],
        }

        if proof_type == "FILE":
            payload["fileName"] = (
                evidence.file_name or f"mergeguide-evidence-{evidence.evidence_id}.json"
            )
            payload["fileContent"] = evidence.content  # Base64 encoded
        else:
            payload["text"] = evidence.content

        # Add metadata in description
        metadata_str = (
            f"\n\nMergeGuide Metadata:\n"
            f"- Evaluation ID: {evidence.evaluation_id}\n"
            f"- Repository: {evidence.repository}\n"
            f"- Policy: {evidence.policy_name} ({evidence.policy_id})\n"
            f"- Status: {'PASS' if evidence.passed else 'FAIL'}\n"
            f"- Violations: {evidence.violation_count}"
        )
        payload["description"] += metadata_str

        return payload

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