from typing import Annotated, Literal
from httpx import Client
from pydantic import BaseModel

from admyral.action import action, ArgumentMetadata
from admyral.context import ctx
from admyral.typings import JsonValue
from admyral.secret.secret import register_secret


@register_secret(secret_type="Snyk")
class SnykSecret(BaseModel):
    api_token: str
    region: Literal["us", "eu", "au"]


def get_snyk_client(secret: SnykSecret) -> Client:
    return Client(
        base_url=_get_base_api_url(secret.region),
        headers={
            "Authorization": f"token {secret.api_token}",
            "Content-Type": "application/json",
            "Accept": "application/json",
        },
    )


@action(
    display_name="List Vulnerabilities",
    display_namespace="Snyk",
    description="List Snyk vulnerabilities",
    secrets_placeholders=["SNYK_SECRET"],
)
def list_snyk_vulnerabilities(
    org_id: Annotated[
        str,
        ArgumentMetadata(
            display_name="Organization ID",
            description="The ID of the organization to list vulnerabilities for",
        ),
    ],
    start_time: Annotated[
        str | None,
        ArgumentMetadata(
            display_name="Start Time",
            description="The start time of the vulnerabilities in ISO-8601 format (inclusive)",
        ),
    ] = None,
    end_time: Annotated[
        str | None,
        ArgumentMetadata(
            display_name="End Time",
            description="The end time of the vulnerabilities in ISO-8601 format (inclusive)",
        ),
    ] = None,
    limit: Annotated[
        int | None,
        ArgumentMetadata(
            display_name="Limit",
            description="The maximum number of vulnerabilities to return.",
        ),
    ] = 1000,
) -> list[dict[str, JsonValue]]:
    # https://apidocs.snyk.io/?version=2024-06-21#get-/orgs/-org_id-/issues

    secret = ctx.get().secrets.get("SNYK_SECRET")
    secret = SnykSecret.model_validate(secret)

    params = {}
    if start_time:
        params["created_after"] = start_time
    if end_time:
        params["created_before"] = end_time
    if limit:
        params["limit"] = limit

    with get_snyk_client(secret) as client:
        response = client.get(f"/rest/orgs/{org_id}/issues", params=params)
        response.raise_for_status()

        # handle pagination
        result = response.json()
        data = result.get("data", [])

        next_url = _get_next_url(result)
        while next_url:
            next_url = next_url.replace(str(client.base_url), "")
            response = client.get(
                next_url,
            )
            response.raise_for_status()
            result = response.json()

            data.extend(result.get("data", []))
            next_url = _get_next_url(result)

        return data


def _get_next_url(result: dict) -> str | None:
    if "links" in result and "next" in result["links"]:
        next_url = result["links"]["next"]
        if not isinstance(next_url, str):
            next_url = next_url["href"]
        return next_url
    return None


def _get_base_api_url(region: str) -> str:
    match region.lower():
        case "au":
            return "https://api.au.snyk.io"
        case "eu":
            return "https://api.eu.snyk.io"
        case "us":
            return "https://api.snyk.io"
        case _:
            raise ValueError("Invalid region. Must be one of 'us', 'eu', or 'au'.")
