"""
CRUD ENDPOINTS FOR POLICIES

Provides REST API endpoints for managing policies and policy attachments.
"""

from typing import Optional

from fastapi import APIRouter, Depends, HTTPException

from litellm._logging import verbose_proxy_logger
from litellm.proxy._types import UserAPIKeyAuth
from litellm.proxy.auth.user_api_key_auth import user_api_key_auth
from litellm.proxy.policy_engine.attachment_registry import \
    get_attachment_registry
from litellm.proxy.policy_engine.pipeline_executor import PipelineExecutor
from litellm.proxy.policy_engine.policy_registry import get_policy_registry
from litellm.types.proxy.policy_engine import (
    GuardrailPipeline, PipelineTestRequest, PolicyAttachmentCreateRequest,
    PolicyAttachmentDBResponse, PolicyAttachmentListResponse,
    PolicyCreateRequest, PolicyDBResponse, PolicyListDBResponse,
    PolicyUpdateRequest, PolicyVersionCompareResponse,
    PolicyVersionCreateRequest, PolicyVersionListResponse,
    PolicyVersionStatusUpdateRequest)

router = APIRouter()


# ─────────────────────────────────────────────────────────────────────────────
# Policy CRUD Endpoints
# ─────────────────────────────────────────────────────────────────────────────


@router.get(
    "/policies/list",
    tags=["Policies"],
    dependencies=[Depends(user_api_key_auth)],
    response_model=PolicyListDBResponse,
)
async def list_policies(version_status: Optional[str] = None):
    """
    List all policies from the database. Optionally filter by version_status.

    Query params:
    - version_status: Optional. One of "draft", "published", "production".
      If omitted, all versions are returned.

    Example Request:
    ```bash
    curl -X GET "http://localhost:4000/policies/list" \\
        -H "Authorization: Bearer <your_api_key>"
    curl -X GET "http://localhost:4000/policies/list?version_status=production" \\
        -H "Authorization: Bearer <your_api_key>"
    ```

    Example Response:
    ```json
    {
        "policies": [
            {
                "policy_id": "123e4567-e89b-12d3-a456-426614174000",
                "policy_name": "global-baseline",
                "version_number": 1,
                "version_status": "production",
                "inherit": null,
                "description": "Base guardrails for all requests",
                "guardrails_add": ["pii_masking"],
                "guardrails_remove": [],
                "condition": null,
                "created_at": "2024-01-01T00:00:00Z",
                "updated_at": "2024-01-01T00:00:00Z"
            }
        ],
        "total_count": 1
    }
    ```
    """
    from litellm.proxy.proxy_server import prisma_client

    if prisma_client is None:
        raise HTTPException(status_code=500, detail="Database not connected")

    try:
        policies = await get_policy_registry().get_all_policies_from_db(
            prisma_client, version_status=version_status
        )
        return PolicyListDBResponse(policies=policies, total_count=len(policies))
    except Exception as e:
        verbose_proxy_logger.exception(f"Error listing policies: {e}")
        raise HTTPException(status_code=500, detail=str(e))


@router.post(
    "/policies",
    tags=["Policies"],
    dependencies=[Depends(user_api_key_auth)],
    response_model=PolicyDBResponse,
)
async def create_policy(
    request: PolicyCreateRequest,
    user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth),
):
    """
    Create a new policy.

    Example Request:
    ```bash
    curl -X POST "http://localhost:4000/policies" \\
        -H "Authorization: Bearer <your_api_key>" \\
        -H "Content-Type: application/json" \\
        -d '{
            "policy_name": "global-baseline",
            "description": "Base guardrails for all requests",
            "guardrails_add": ["pii_masking", "prompt_injection"],
            "guardrails_remove": []
        }'
    ```

    Example Response:
    ```json
    {
        "policy_id": "123e4567-e89b-12d3-a456-426614174000",
        "policy_name": "global-baseline",
        "inherit": null,
        "description": "Base guardrails for all requests",
        "guardrails_add": ["pii_masking", "prompt_injection"],
        "guardrails_remove": [],
        "condition": null,
        "created_at": "2024-01-01T00:00:00Z",
        "updated_at": "2024-01-01T00:00:00Z"
    }
    ```
    """
    from litellm.proxy.proxy_server import prisma_client

    if prisma_client is None:
        raise HTTPException(status_code=500, detail="Database not connected")

    try:
        created_by = user_api_key_dict.user_id
        result = await get_policy_registry().add_policy_to_db(
            policy_request=request,
            prisma_client=prisma_client,
            created_by=created_by,
        )
        return result
    except Exception as e:
        verbose_proxy_logger.exception(f"Error creating policy: {e}")
        if "unique constraint" in str(e).lower():
            raise HTTPException(
                status_code=400,
                detail=f"Policy with name '{request.policy_name}' already exists",
            )
        raise HTTPException(status_code=500, detail=str(e))


# ─────────────────────────────────────────────────────────────────────────────
# Policy Versioning Endpoints (must be before /policies/{policy_id} to avoid path conflicts)
# ─────────────────────────────────────────────────────────────────────────────


@router.get(
    "/policies/name/{policy_name}/versions",
    tags=["Policies"],
    dependencies=[Depends(user_api_key_auth)],
    response_model=PolicyVersionListResponse,
)
async def list_policy_versions(policy_name: str):
    """
    List all versions of a policy by name, ordered by version_number descending.
    """
    from litellm.proxy.proxy_server import prisma_client

    if prisma_client is None:
        raise HTTPException(status_code=500, detail="Database not connected")

    try:
        return await get_policy_registry().get_versions_by_policy_name(
            policy_name=policy_name,
            prisma_client=prisma_client,
        )
    except Exception as e:
        verbose_proxy_logger.exception(f"Error listing policy versions: {e}")
        raise HTTPException(status_code=500, detail=str(e))


@router.post(
    "/policies/name/{policy_name}/versions",
    tags=["Policies"],
    dependencies=[Depends(user_api_key_auth)],
    response_model=PolicyDBResponse,
)
async def create_policy_version(
    policy_name: str,
    request: PolicyVersionCreateRequest,
    user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth),
):
    """
    Create a new draft version of a policy. Copies all fields from the source.
    Source is current production if source_policy_id is not provided.
    """
    from litellm.proxy.proxy_server import prisma_client

    if prisma_client is None:
        raise HTTPException(status_code=500, detail="Database not connected")

    try:
        created_by = user_api_key_dict.user_id
        return await get_policy_registry().create_new_version(
            policy_name=policy_name,
            prisma_client=prisma_client,
            source_policy_id=request.source_policy_id,
            created_by=created_by,
        )
    except Exception as e:
        verbose_proxy_logger.exception(f"Error creating policy version: {e}")
        if "not found" in str(e).lower() or "no production" in str(e).lower():
            raise HTTPException(status_code=404, detail=str(e))
        raise HTTPException(status_code=500, detail=str(e))


@router.put(
    "/policies/{policy_id}/status",
    tags=["Policies"],
    dependencies=[Depends(user_api_key_auth)],
    response_model=PolicyDBResponse,
)
async def update_policy_version_status(
    policy_id: str,
    request: PolicyVersionStatusUpdateRequest,
    user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth),
):
    """
    Update a policy version's status. Valid transitions:
    - draft -> published
    - published -> production (demotes current production to published)
    - production -> published (demotes, policy becomes inactive)
    """
    from litellm.proxy.proxy_server import prisma_client

    if prisma_client is None:
        raise HTTPException(status_code=500, detail="Database not connected")

    try:
        updated_by = user_api_key_dict.user_id
        return await get_policy_registry().update_version_status(
            policy_id=policy_id,
            new_status=request.version_status,
            prisma_client=prisma_client,
            updated_by=updated_by,
        )
    except HTTPException:
        raise
    except Exception as e:
        verbose_proxy_logger.exception(f"Error updating version status: {e}")
        if "invalid status" in str(e).lower() or "only draft" in str(e).lower() or "cannot promote" in str(e).lower():
            raise HTTPException(status_code=400, detail=str(e))
        if "not found" in str(e).lower():
            raise HTTPException(status_code=404, detail=str(e))
        raise HTTPException(status_code=500, detail=str(e))


@router.get(
    "/policies/compare",
    tags=["Policies"],
    dependencies=[Depends(user_api_key_auth)],
    response_model=PolicyVersionCompareResponse,
)
async def compare_policy_versions(
    version_a: str,
    version_b: str,
):
    """
    Compare two policy versions. Query params: version_a, version_b (policy version IDs).
    """
    from litellm.proxy.proxy_server import prisma_client

    if prisma_client is None:
        raise HTTPException(status_code=500, detail="Database not connected")

    try:
        return await get_policy_registry().compare_versions(
            policy_id_a=version_a,
            policy_id_b=version_b,
            prisma_client=prisma_client,
        )
    except Exception as e:
        verbose_proxy_logger.exception(f"Error comparing versions: {e}")
        if "not found" in str(e).lower():
            raise HTTPException(status_code=404, detail=str(e))
        raise HTTPException(status_code=500, detail=str(e))


@router.delete(
    "/policies/name/{policy_name}/all-versions",
    tags=["Policies"],
    dependencies=[Depends(user_api_key_auth)],
)
async def delete_all_policy_versions(policy_name: str):
    """
    Delete all versions of a policy. Also removes from in-memory registry.
    """
    from litellm.proxy.proxy_server import prisma_client

    if prisma_client is None:
        raise HTTPException(status_code=500, detail="Database not connected")

    try:
        return await get_policy_registry().delete_all_versions(
            policy_name=policy_name,
            prisma_client=prisma_client,
        )
    except Exception as e:
        verbose_proxy_logger.exception(f"Error deleting all versions: {e}")
        raise HTTPException(status_code=500, detail=str(e))


# ─────────────────────────────────────────────────────────────────────────────
# Policy CRUD by ID
# ─────────────────────────────────────────────────────────────────────────────


@router.get(
    "/policies/{policy_id}",
    tags=["Policies"],
    dependencies=[Depends(user_api_key_auth)],
    response_model=PolicyDBResponse,
)
async def get_policy(policy_id: str):
    """
    Get a policy by ID.

    Example Request:
    ```bash
    curl -X GET "http://localhost:4000/policies/123e4567-e89b-12d3-a456-426614174000" \\
        -H "Authorization: Bearer <your_api_key>"
    ```
    """
    from litellm.proxy.proxy_server import prisma_client

    if prisma_client is None:
        raise HTTPException(status_code=500, detail="Database not connected")

    try:
        result = await get_policy_registry().get_policy_by_id_from_db(
            policy_id=policy_id,
            prisma_client=prisma_client,
        )
        if result is None:
            raise HTTPException(
                status_code=404, detail=f"Policy with ID {policy_id} not found"
            )
        return result
    except HTTPException:
        raise
    except Exception as e:
        verbose_proxy_logger.exception(f"Error getting policy: {e}")
        raise HTTPException(status_code=500, detail=str(e))


@router.put(
    "/policies/{policy_id}",
    tags=["Policies"],
    dependencies=[Depends(user_api_key_auth)],
    response_model=PolicyDBResponse,
)
async def update_policy(
    policy_id: str,
    request: PolicyUpdateRequest,
    user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth),
):
    """
    Update an existing policy.

    Example Request:
    ```bash
    curl -X PUT "http://localhost:4000/policies/123e4567-e89b-12d3-a456-426614174000" \\
        -H "Authorization: Bearer <your_api_key>" \\
        -H "Content-Type: application/json" \\
        -d '{
            "description": "Updated description",
            "guardrails_add": ["pii_masking", "toxicity_filter"]
        }'
    ```
    """
    from litellm.proxy.proxy_server import prisma_client

    if prisma_client is None:
        raise HTTPException(status_code=500, detail="Database not connected")

    try:
        # Check if policy exists and is draft (only drafts can be updated)
        existing = await get_policy_registry().get_policy_by_id_from_db(
            policy_id=policy_id,
            prisma_client=prisma_client,
        )
        if existing is None:
            raise HTTPException(
                status_code=404, detail=f"Policy with ID {policy_id} not found"
            )
        if getattr(existing, "version_status", "production") != "draft":
            raise HTTPException(
                status_code=400,
                detail="Only draft versions can be updated. Publish or create a new version to change published/production.",
            )

        updated_by = user_api_key_dict.user_id
        result = await get_policy_registry().update_policy_in_db(
            policy_id=policy_id,
            policy_request=request,
            prisma_client=prisma_client,
            updated_by=updated_by,
        )
        return result
    except HTTPException:
        raise
    except Exception as e:
        verbose_proxy_logger.exception(f"Error updating policy: {e}")
        raise HTTPException(status_code=500, detail=str(e))


@router.delete(
    "/policies/{policy_id}",
    tags=["Policies"],
    dependencies=[Depends(user_api_key_auth)],
)
async def delete_policy(policy_id: str):
    """
    Delete a policy.

    Example Request:
    ```bash
    curl -X DELETE "http://localhost:4000/policies/123e4567-e89b-12d3-a456-426614174000" \\
        -H "Authorization: Bearer <your_api_key>"
    ```

    Example Response:
    ```json
    {
        "message": "Policy 123e4567-e89b-12d3-a456-426614174000 deleted successfully"
    }
    ```
    """
    from litellm.proxy.proxy_server import prisma_client

    if prisma_client is None:
        raise HTTPException(status_code=500, detail="Database not connected")

    try:
        # Check if policy exists
        existing = await get_policy_registry().get_policy_by_id_from_db(
            policy_id=policy_id,
            prisma_client=prisma_client,
        )
        if existing is None:
            raise HTTPException(
                status_code=404, detail=f"Policy with ID {policy_id} not found"
            )

        result = await get_policy_registry().delete_policy_from_db(
            policy_id=policy_id,
            prisma_client=prisma_client,
        )
        # Result may include "warning" if production was deleted
        return result
    except HTTPException:
        raise
    except Exception as e:
        verbose_proxy_logger.exception(f"Error deleting policy: {e}")
        raise HTTPException(status_code=500, detail=str(e))


@router.get(
    "/policies/{policy_id}/resolved-guardrails",
    tags=["Policies"],
    dependencies=[Depends(user_api_key_auth)],
)
async def get_resolved_guardrails(policy_id: str):
    """
    Get the resolved guardrails for a policy (including inherited guardrails).

    This endpoint resolves the full inheritance chain and returns the final
    set of guardrails that would be applied for this policy.

    Example Request:
    ```bash
    curl -X GET "http://localhost:4000/policies/123e4567-e89b-12d3-a456-426614174000/resolved-guardrails" \\
        -H "Authorization: Bearer <your_api_key>"
    ```

    Example Response:
    ```json
    {
        "policy_id": "123e4567-e89b-12d3-a456-426614174000",
        "policy_name": "healthcare-compliance",
        "resolved_guardrails": ["pii_masking", "prompt_injection", "toxicity_filter"]
    }
    ```
    """
    from litellm.proxy.proxy_server import prisma_client

    if prisma_client is None:
        raise HTTPException(status_code=500, detail="Database not connected")

    try:
        # Get the policy
        policy = await get_policy_registry().get_policy_by_id_from_db(
            policy_id=policy_id,
            prisma_client=prisma_client,
        )
        if policy is None:
            raise HTTPException(
                status_code=404, detail=f"Policy with ID {policy_id} not found"
            )

        # Resolve guardrails
        resolved = await get_policy_registry().resolve_guardrails_from_db(
            policy_name=policy.policy_name,
            prisma_client=prisma_client,
        )

        return {
            "policy_id": policy.policy_id,
            "policy_name": policy.policy_name,
            "resolved_guardrails": resolved,
        }
    except HTTPException:
        raise
    except ValueError as e:
        raise HTTPException(status_code=400, detail=str(e))
    except Exception as e:
        verbose_proxy_logger.exception(f"Error resolving guardrails: {e}")
        raise HTTPException(status_code=500, detail=str(e))


# ─────────────────────────────────────────────────────────────────────────────
# Pipeline Test Endpoint
# ─────────────────────────────────────────────────────────────────────────────


@router.post(
    "/policies/test-pipeline",
    tags=["Policies"],
    dependencies=[Depends(user_api_key_auth)],
)
async def test_pipeline(
    request: PipelineTestRequest,
    user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth),
):
    """
    Test a guardrail pipeline with sample messages.

    Executes the pipeline steps against the provided test messages and returns
    step-by-step results showing which guardrails passed/failed, actions taken,
    and timing information.

    Example Request:
    ```bash
    curl -X POST "http://localhost:4000/policies/test-pipeline" \\
        -H "Authorization: Bearer <your_api_key>" \\
        -H "Content-Type: application/json" \\
        -d '{
            "pipeline": {
                "mode": "pre_call",
                "steps": [
                    {"guardrail": "pii-guard", "on_pass": "next", "on_fail": "block"}
                ]
            },
            "test_messages": [{"role": "user", "content": "My SSN is 123-45-6789"}]
        }'
    ```
    """
    try:
        validated_pipeline = GuardrailPipeline(**request.pipeline)
    except Exception as e:
        raise HTTPException(status_code=400, detail=f"Invalid pipeline: {e}")

    data = {
        "messages": request.test_messages,
        "model": "test",
        "metadata": {},
    }

    try:
        result = await PipelineExecutor.execute_steps(
            steps=validated_pipeline.steps,
            mode=validated_pipeline.mode,
            data=data,
            user_api_key_dict=user_api_key_dict,
            call_type="completion",
            policy_name="test-pipeline",
        )
        return result.model_dump()
    except Exception as e:
        verbose_proxy_logger.exception(f"Error testing pipeline: {e}")
        raise HTTPException(status_code=500, detail=str(e))


# ─────────────────────────────────────────────────────────────────────────────
# Policy Attachment CRUD Endpoints
# ─────────────────────────────────────────────────────────────────────────────


@router.get(
    "/policies/attachments/list",
    tags=["Policies"],
    dependencies=[Depends(user_api_key_auth)],
    response_model=PolicyAttachmentListResponse,
)
async def list_policy_attachments():
    """
    List all policy attachments from the database.

    Example Request:
    ```bash
    curl -X GET "http://localhost:4000/policies/attachments/list" \\
        -H "Authorization: Bearer <your_api_key>"
    ```

    Example Response:
    ```json
    {
        "attachments": [
            {
                "attachment_id": "123e4567-e89b-12d3-a456-426614174000",
                "policy_name": "global-baseline",
                "scope": "*",
                "teams": [],
                "keys": [],
                "models": [],
                "created_at": "2024-01-01T00:00:00Z",
                "updated_at": "2024-01-01T00:00:00Z"
            }
        ],
        "total_count": 1
    }
    ```
    """
    from litellm.proxy.proxy_server import prisma_client

    if prisma_client is None:
        raise HTTPException(status_code=500, detail="Database not connected")

    try:
        attachments = await get_attachment_registry().get_all_attachments_from_db(
            prisma_client
        )
        return PolicyAttachmentListResponse(
            attachments=attachments, total_count=len(attachments)
        )
    except Exception as e:
        verbose_proxy_logger.exception(f"Error listing policy attachments: {e}")
        raise HTTPException(status_code=500, detail=str(e))


@router.post(
    "/policies/attachments",
    tags=["Policies"],
    dependencies=[Depends(user_api_key_auth)],
    response_model=PolicyAttachmentDBResponse,
)
async def create_policy_attachment(
    request: PolicyAttachmentCreateRequest,
    user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth),
):
    """
    Create a new policy attachment.

    Example Request:
    ```bash
    curl -X POST "http://localhost:4000/policies/attachments" \\
        -H "Authorization: Bearer <your_api_key>" \\
        -H "Content-Type: application/json" \\
        -d '{
            "policy_name": "global-baseline",
            "scope": "*"
        }'
    ```

    Example with team-specific attachment:
    ```bash
    curl -X POST "http://localhost:4000/policies/attachments" \\
        -H "Authorization: Bearer <your_api_key>" \\
        -H "Content-Type: application/json" \\
        -d '{
            "policy_name": "healthcare-compliance",
            "teams": ["healthcare-team", "medical-research"]
        }'
    ```

    Example Response:
    ```json
    {
        "attachment_id": "123e4567-e89b-12d3-a456-426614174000",
        "policy_name": "global-baseline",
        "scope": "*",
        "teams": [],
        "keys": [],
        "models": [],
        "created_at": "2024-01-01T00:00:00Z",
        "updated_at": "2024-01-01T00:00:00Z"
    }
    ```
    """
    from litellm.proxy.proxy_server import prisma_client

    if prisma_client is None:
        raise HTTPException(status_code=500, detail="Database not connected")

    try:
        # Verify the policy has a production version (attachments resolve against production)
        policies = await get_policy_registry().get_all_policies_from_db(
            prisma_client, version_status="production"
        )
        policy_names = {p.policy_name for p in policies}
        if request.policy_name not in policy_names:
            raise HTTPException(
                status_code=404,
                detail=f"Policy '{request.policy_name}' not found. Create the policy first.",
            )

        created_by = user_api_key_dict.user_id
        result = await get_attachment_registry().add_attachment_to_db(
            attachment_request=request,
            prisma_client=prisma_client,
            created_by=created_by,
        )
        return result
    except HTTPException:
        raise
    except Exception as e:
        verbose_proxy_logger.exception(f"Error creating policy attachment: {e}")
        raise HTTPException(status_code=500, detail=str(e))


@router.get(
    "/policies/attachments/{attachment_id}",
    tags=["Policies"],
    dependencies=[Depends(user_api_key_auth)],
    response_model=PolicyAttachmentDBResponse,
)
async def get_policy_attachment(attachment_id: str):
    """
    Get a policy attachment by ID.

    Example Request:
    ```bash
    curl -X GET "http://localhost:4000/policies/attachments/123e4567-e89b-12d3-a456-426614174000" \\
        -H "Authorization: Bearer <your_api_key>"
    ```
    """
    from litellm.proxy.proxy_server import prisma_client

    if prisma_client is None:
        raise HTTPException(status_code=500, detail="Database not connected")

    try:
        result = await get_attachment_registry().get_attachment_by_id_from_db(
            attachment_id=attachment_id,
            prisma_client=prisma_client,
        )
        if result is None:
            raise HTTPException(
                status_code=404,
                detail=f"Attachment with ID {attachment_id} not found",
            )
        return result
    except HTTPException:
        raise
    except Exception as e:
        verbose_proxy_logger.exception(f"Error getting policy attachment: {e}")
        raise HTTPException(status_code=500, detail=str(e))


@router.delete(
    "/policies/attachments/{attachment_id}",
    tags=["Policies"],
    dependencies=[Depends(user_api_key_auth)],
)
async def delete_policy_attachment(attachment_id: str):
    """
    Delete a policy attachment.

    Example Request:
    ```bash
    curl -X DELETE "http://localhost:4000/policies/attachments/123e4567-e89b-12d3-a456-426614174000" \\
        -H "Authorization: Bearer <your_api_key>"
    ```

    Example Response:
    ```json
    {
        "message": "Attachment 123e4567-e89b-12d3-a456-426614174000 deleted successfully"
    }
    ```
    """
    from litellm.proxy.proxy_server import prisma_client

    if prisma_client is None:
        raise HTTPException(status_code=500, detail="Database not connected")

    try:
        # Check if attachment exists
        existing = await get_attachment_registry().get_attachment_by_id_from_db(
            attachment_id=attachment_id,
            prisma_client=prisma_client,
        )
        if existing is None:
            raise HTTPException(
                status_code=404,
                detail=f"Attachment with ID {attachment_id} not found",
            )

        result = await get_attachment_registry().delete_attachment_from_db(
            attachment_id=attachment_id,
            prisma_client=prisma_client,
        )
        return result
    except HTTPException:
        raise
    except Exception as e:
        verbose_proxy_logger.exception(f"Error deleting policy attachment: {e}")
        raise HTTPException(status_code=500, detail=str(e))
