import json
import logging
from typing import Any, Optional

import requests

from pagerduty_api import get_logger, log_it


class AbstractClient:
    def __init__(
        self,
        http_session: requests.Session = requests.Session(),
        endpoint: str = "https://api.pagerduty.com",
        auth_token: Optional[str] = None,
        logger: Optional[logging.Logger] = None,
    ):
        self._endpoint = endpoint
        self._http_session = http_session
        self._auth_token = auth_token
        self._valid_methods = ["get", "patch", "post", "put", "head", "delete"]
        self._logger = logger or get_logger()

    @log_it
    def do_request(
        self,
        url: str,
        entity_name: str,
        headers: dict[str, str] = {},
        params: dict[str, Any] = {},
        paginate: bool = True,
        method: str = "get",
        data: Optional[Any] = None,
    ) -> list[Any]:
        assert url, f"Cannot do HTTP request against url: {url}"
        assert entity_name, f"Cannot do HTTP request with entity: {entity_name}"
        assert method in self._valid_methods, f'"{method}" is not a valid request method'
        headers = self._set_default_headers(headers)
        params = self._set_default_params(params)
        pages = {"more": True, "limit": params["limit"], "offset": params["offset"]}

        # FIXME: use a logger
        # print(f'Executing HTTP {method} on {url}')

        records = []
        while pages.get("more"):  # we always make at least one call...
            response = getattr(self._http_session, method)(
                url, headers=headers, params=params, data=data
            )

            # self._logger.info(response.request.headers)
            # self._logger.info(f'{response.request.method} {response.request.url}')
            if response.status_code in [204, 400, 404, 500]:  # return no content
                # FIXME: use a logger
                print(
                    f"HTTP response returned with status {response.status_code}. Returning empty records."
                )
                return records

            pages = response.json()
            if "offset" in pages:
                records.extend(pages[entity_name])
                params["offset"] = pages["offset"] + pages["limit"]
            elif "ok" not in pages:
                records.append(pages[entity_name])

            if not paginate or method != "get":  # disable pagination
                break

        return records

    @log_it
    def do_analytics_request(
        self,
        url: str,
        entity_name: str,
        filters: dict[str, Any] = {},
        method: str = "post",
        limit: Optional[int] = None,
        paginate: bool = False,
        aggregate_unit: Optional[str] = None,
    ) -> list[Any]:
        assert url, f"Cannot do HTTP request against url: {url}"
        assert entity_name, f"Cannot do HTTP request with entity: {entity_name}"
        assert method in self._valid_methods, f'"{method}" is not a valid request method'
        method = method.upper()

        headers = {
            "x-early-access": "analytics-v2",
            "accept": "application/vnd.pagerduty+json;version=2",
            "content-type": "application/json",
            "authorization": f"Token token={self._auth_token}",
        }
        payload = self._set_default_analytics_data(
            filters=filters,
            aggregate_unit=aggregate_unit if aggregate_unit else None if "raw" in url else "day",
            limit=limit,
        )

        data, more = [], True
        while more:
            self._logger.info(
                f"Executing {method} on {url} with data {payload} and headers {headers}"
            )
            response = requests.request(method, url, data=json.dumps(payload), headers=headers)
            if response.status_code in [204, 400, 404, 500]:  # return no content
                self._logger.error(
                    f"HTTP response returned with status {response.status_code}. Returning empty records."
                )
                return []
            response.raise_for_status()
            resp_json = response.json()
            data.extend(resp_json.get(entity_name) or [])
            more = paginate and resp_json.get("more") and resp_json.get("last")
            if more:
                payload["starting_after"] = resp_json["last"]
        return data

    @log_it
    def _set_default_headers(self, headers: dict[str, str]) -> dict[str, str]:
        """Set the default Accept, Authorization, and Content-type headers if not already present"""
        cp = {} if headers is None else headers.copy()
        cp["Accept"] = cp.get("Accept", "application/vnd.pagerduty+json;version=2")
        cp["Authorization"] = cp.get("Authorization", f"Token token={self._auth_token}")
        cp["Content-type"] = cp.get("Content-type", "application/json")
        return cp

    @log_it
    def _set_default_params(self, params: dict[str, Any]) -> dict[str, Any]:
        """Set the default limit and offset params if not already present"""
        cp = {} if params is None else params.copy()
        cp["limit"] = cp.get("limit", 100)
        cp["offset"] = cp.get("offset", 0)
        return cp

    @log_it
    def _set_default_analytics_data(
        self,
        filters: dict[str, Any],
        aggregate_unit: str = "day",
        urgency: str = "high",
        limit: Optional[int] = None,
    ) -> dict[str, Any]:
        """Set the default limit and offset params if not already present"""
        data = {"filters": filters}
        if urgency:
            data["filters"] = data["filters"] or {}
            data["filters"]["urgency"] = urgency
        if aggregate_unit:
            data["aggregate_unit"] = aggregate_unit
        if limit:
            data["limit"] = limit
        return data

    @log_it
    def get_entity_tags(self, url: str, entity_id: str, params: Optional[dict[str, Any]] = None):
        assert entity_id, f"Cannot get entity tags with entity id: {entity_id}"
        url = url if url else f"{self._endpoint}/{self._default_entity_name}"
        resp = self.do_request(url=f"{url}/{entity_id}/tags", entity_name="tags", params=params)

        tag = None
        if len(resp) == 1:
            tag = resp[0]
        return tag

    @log_it
    def modify_entity_tags(self, url: str, entity_id: str, assignments: dict[Any, Any] = None):
        assert url, f"Cannot modify entity tags with url: {url}"
        assert entity_id, f"Cannot modify entity tags with entity id: {entity_id}"
        return self.do_request(
            url=f"{url}/{entity_id}/change_tags",
            entity_name="tag",
            method="post",
            data=json.dumps(assignments),
        )
