import logging
import sys
from typing import List, Dict, Any
from typing import Optional
from typing import Union

import httpx
from pydantic import parse_obj_as

from bulk_restore_tool.__about__ import __version__
from bulk_restore_tool._auth import APIClientAuth
from bulk_restore_tool._log_util import LogUtil
from bulk_restore_tool.models import Archive
from bulk_restore_tool.models import DetailType
from bulk_restore_tool._settings import BRTSettings

_user_agent = f"bulk-restore-tool {__version__}"


class Client:
    """
    An HTTP client for interacting with Code42 Restore APIs.

    **Parameters**:

    * **url**: `str` The url of your Code42 Console.
    * **api_client_id**: `str` The ID of your [Code42 API Client](https://support.code42.com/Incydr/Admin/Code42_console_reference/API_clients)
    * **api_client_secret**: `str` The Secret for your Code42 API Client.
    """

    def __init__(
        self,
        url: Optional[str] = None,
        username: Optional[str] = None,
        password: Optional[str] = None,
        api_client_id: Optional[str] = None,
        api_client_secret: Optional[str] = None,
        **settings_kwargs,
    ):
        self.settings = BRTSettings(
            url=url,
            username=username,
            password=password,
            api_client_id=api_client_id,
            api_client_secret=api_client_secret,
            **settings_kwargs,
        )
        auth = APIClientAuth(  # type: ignore
            url=self.settings.url,
            api_client_id=self.settings.api_client_id,  # type: ignore
            api_client_secret=self.settings.api_client_secret.get_secret_value(),  # type: ignore
        )
        self.config = {
            "auth": auth,
            "headers": {"User-Agent": _user_agent},
            "event_hooks": {
                "response": [lambda response: response.raise_for_status()],
                "request": [],
            },
            "timeout": 60,
        }
        self.log_util = LogUtil(self.settings.logger)
        self._httpx = httpx.Client(base_url=self.settings.url, **self.config)  # type: ignore
        # test auth on init to fail fast if invalid
        try:
            self._httpx.get("/api/v3/customer/my")
        except httpx.ConnectError as exc:
            # Failed to establish a connection.
            self.log_util.print_and_log(
                f"Unable to resolve host {exc.request.url.host}.\n"
                f"Verify server URL is configured properly"
            )
            sys.exit(1)

        except httpx.HTTPStatusError as exc:
            if exc.response.status_code == 401:
                self.log_util.print_and_log(
                    "Connection attempt was rejected. Caller is not authorized. (response 401)\n"
                    "A common cause of this error includes:\n"
                    " - Unrecognized API Client\n"
                )
                sys.exit(1)

            elif exc.response.status_code == 403:
                self.log_util.print_and_log(
                    "Connection attempt was rejected. Caller is not authorized. (response 403)\n"
                    "A common causes of this error include:\n"
                    " - API Client used does not have the bulk restore permissions\n"
                    " - Bulk restore is not enabled for the tenant\n"
                )
                sys.exit(1)

            elif exc.response.status_code == 404:
                self.log_util.print_and_log(
                    "Unable to connect to host {exc.request.url.host}. Verify server URL is configured properly.\n"
                    "A common cause of this error includes:\n"
                    " - Using the 'api' url instead of the 'console' url"
                )
                sys.exit(1)

            else:
                raise exc

        self.storage_connections = {}  # type: ignore

    def get_storage_connection(self, storage_url: str):
        if storage_url in self.storage_connections:
            return self.storage_connections[storage_url]
        else:
            return self.storage_connections.setdefault(
                storage_url, httpx.Client(**self.config, base_url=storage_url)  # type: ignore
            )

    def create_bulk_restore_session(self, archive: Archive, name: str) -> str:
        storage = self.get_storage_connection(archive.storageNodeWebUrl)
        r = storage.post(
            f"/api/v3/bulk-restore/session/{archive.archiveId}", json={"name": name}
        )
        return r.json()["data"]["bulkRestoreSessionId"]

    def complete_bulk_restore_session(
        self, archive: Archive, session_id: str
    ) -> httpx.Response:
        storage = self.get_storage_connection(archive.storageNodeWebUrl)
        return storage.post(f"/api/v3/bulk-restore/session/{session_id}/complete")

    def get_archive_details(
        self, identifier: str, id_type: Union[str, DetailType]
    ) -> List[Archive]:
        self.log_util.log(
            "Requesting archive details: identifier=" + identifier + " type=" + id_type,
            logging.DEBUG,
        )
        id_type = DetailType(id_type)  # will raise if not valid enum member
        id_map = {
            DetailType.ARCHIVE: "archiveId",
            DetailType.DEVICE: "deviceId",
            DetailType.USER: "username",
        }
        try:
            r = self._httpx.get(
                "/api/v3/restorable-archive-details",
                params={id_map[id_type]: identifier},
            )
        except httpx.HTTPStatusError as exc:
            if exc.response.status_code == 401:
                self.log_util.print_and_log(
                    "Connection attempt was rejected. Caller is not authorized. (response 401)\n"
                    "A common cause of this error includes:\n"
                    " - Unrecognized API Client\n"
                )
                sys.exit(1)

            elif exc.response.status_code == 403:
                self.log_util.print_and_log(
                    "Connection attempt was rejected. Caller is not authorized. (response 403)\n"
                    "A common causes of this error include:\n"
                    " - API Client used does not have the bulk restore permissions\n"
                    " - Bulk restore is not enabled for the tenant\n"
                )
                sys.exit(1)

            raise exc

        archive_list = parse_obj_as(
            List[Archive], r.json()["data"]["restorableArchiveDetails"]
        )
        if not archive_list:
            self.log_util.print_and_log(
                f"No restorable archives available: type={id_type} id={identifier}"
            )
            self.log_util.print_and_log(r.json(), logging.DEBUG)
            return []
        else:
            self.log_util.log(
                f"Restorable archives retrieved. type={id_type} id={identifier} archives={archive_list}"
            )
            return archive_list

    def get_archive_metadata(
        self,
        archive: Archive,
        startTime: Optional[int] = None,
        endTime: Optional[int] = None,
        includeDeleted: bool = False,
        includeAllVersions: bool = False,
    ) -> httpx.Response:
        if archive.storageNodeWebUrl is None:
            archive = self.get_archive_details(
                identifier=archive.archiveId, id_type=DetailType.ARCHIVE
            )[0]
        storage = self.get_storage_connection(archive.storageNodeWebUrl)
        params: Dict[str, Any] = {
            "includeDeleted": includeDeleted,
            "includeAllVersions": includeAllVersions,
        }
        if startTime is not None:
            params["startTimeMillis"] = startTime
        if endTime is not None:
            params["endTimeMillis"] = endTime
        return storage.get(
            f"/api/v3/archive-metadata/{archive.archiveId}", params=params
        )

    def get_file(
        self, session_id: str, archive: Archive, fileId: str, versionTimestamp: str
    ) -> httpx.Response:
        if archive.storageNodeWebUrl is None:
            archive = self.get_archive_details(
                identifier=archive.archiveId, id_type=DetailType.ARCHIVE
            )[0]
        storage = self.get_storage_connection(archive.storageNodeWebUrl)
        params = {
            "bulkRestoreSessionId": session_id,
            "archiveGuid": archive.archiveId,
            "fileId": fileId,
            "versionTimestamp": versionTimestamp,
        }
        return storage.get("/api/v3/bulk-restore-file-data", params=params)
