import typing
from json import JSONDecodeError
from urllib.parse import urljoin

import requests
from django.conf import settings

from common.constants.plugin_ids import PluginID


class IncidentDetails(typing.TypedDict):
    # https://grafana.com/docs/grafana-cloud/alerting-and-irm/irm/incident/api/reference/#getincidentresponse
    createdByUser: typing.Dict
    createdTime: str
    durationSeconds: int
    heroImagePath: str
    incidentEnd: str
    incidentID: str
    incidentMembership: typing.Dict
    incidentStart: str
    isDrill: bool
    labels: typing.List[dict]
    overviewURL: str
    severity: str
    status: str
    summary: str
    taskList: typing.Dict
    title: str


class SeverityDetails(typing.TypedDict):
    severityID: str
    orgID: str
    displayLabel: str
    level: int
    iconName: str
    description: str
    darkColor: str
    lightColor: str
    archivedAt: str
    archivedByUserID: str | None
    deletedAt: str
    deletedByUserID: str | None
    createdAt: str
    updatedAt: str


class ActivityItemDetails(typing.TypedDict):
    # https://grafana.com/docs/grafana-cloud/alerting-and-irm/irm/incident/api/reference/#addactivityresponse
    activityItemID: str
    activityKind: str
    attachments: typing.List[dict]
    body: str
    createdTime: str
    eventTime: str
    fieldValues: typing.Dict[str, str]
    immutable: bool
    incidentID: str
    relevance: str
    subjectUser: typing.Dict[str, str]
    tags: typing.List[str]
    url: str
    user: typing.Dict[str, str]


class IncidentAPIException(Exception):
    def __init__(self, status, url, msg="", method="GET"):
        self.url = url
        self.status = status
        self.method = method
        self.msg = msg

    def __str__(self):
        return f"IncidentAPIException: status={self.status} url={self.url} method={self.method}"


TIMEOUT = 5
DEFAULT_INCIDENT_SEVERITY = "Pending"
DEFAULT_INCIDENT_STATUS = "active"
DEFAULT_ACTIVITY_KIND = "userNote"


class IncidentAPIClient:
    INCIDENT_BASE_PATH = f"/api/plugins/{PluginID.INCIDENT}/resources/"

    def __init__(self, api_url: str, api_token: str) -> None:
        self.api_token = api_token
        self.api_url = urljoin(api_url, self.INCIDENT_BASE_PATH)

    @property
    def _request_headers(self):
        return {"User-Agent": settings.GRAFANA_COM_USER_AGENT, "Authorization": f"Bearer {self.api_token}"}

    def _check_response(self, response: requests.models.Response):
        message = ""

        if response.status_code >= 400:
            try:
                error_data = response.json()
                message = error_data.get("error", response.reason)
            except JSONDecodeError:
                message = response.reason

            raise IncidentAPIException(
                status=response.status_code,
                url=response.request.url,
                msg=message,
                method=response.request.method,
            )

    def create_incident(
        self,
        title: str,
        severity: str = DEFAULT_INCIDENT_SEVERITY,
        status: str = DEFAULT_INCIDENT_STATUS,
        attachCaption: str = "",
        attachURL: str = "",
    ) -> typing.Tuple[IncidentDetails, requests.models.Response]:
        endpoint = "api/v1/IncidentsService.CreateIncident"
        url = self.api_url + endpoint
        # NOTE: invalid severity will raise a 500 error
        response = requests.post(
            url,
            json={
                "title": title,
                "severity": severity,
                "attachCaption": attachCaption,
                "attachURL": attachURL,
                "status": status,
            },
            timeout=TIMEOUT,
            headers=self._request_headers,
        )
        self._check_response(response)
        return response.json().get("incident"), response

    def get_incident(self, incident_id: str) -> typing.Tuple[IncidentDetails, requests.models.Response]:
        endpoint = "api/v1/IncidentsService.GetIncident"
        url = self.api_url + endpoint
        response = requests.post(url, json={"incidentID": incident_id}, timeout=TIMEOUT, headers=self._request_headers)
        self._check_response(response)
        return response.json().get("incident"), response

    def get_severities(self) -> typing.Tuple[typing.List[SeverityDetails], requests.models.Response]:
        # NOTE: internal endpoint
        endpoint = "api/SeveritiesService.GetOrgSeverities"
        url = self.api_url + endpoint
        # pass empty json payload otherwise it will return a 500 response
        response = requests.post(url, timeout=TIMEOUT, headers=self._request_headers, json={})
        self._check_response(response)
        return response.json().get("severities"), response

    def add_activity(
        self, incident_id: str, body: str, kind: str = DEFAULT_ACTIVITY_KIND
    ) -> typing.Tuple[ActivityItemDetails, requests.models.Response]:
        endpoint = "api/v1/ActivityService.AddActivity"
        url = self.api_url + endpoint
        response = requests.post(
            url,
            json={"incidentID": incident_id, "activityKind": kind, "body": body},
            timeout=TIMEOUT,
            headers=self._request_headers,
        )
        self._check_response(response)
        return response.json().get("activityItem"), response
