# Copyright 2022 Cisco Systems, Inc. and its affiliates

"""
Module for handling admintech logs for a device
"""
from __future__ import annotations

import logging
import time
from pathlib import Path
from typing import TYPE_CHECKING, List, Optional

from catalystwan.dataclasses import AdminTech, DeviceAdminTech
from catalystwan.exceptions import CatalystwanException, ManagerHTTPError
from catalystwan.response import ManagerResponse
from catalystwan.utils.creation_tools import create_dataclass

if TYPE_CHECKING:
    from catalystwan.session import ManagerSession

logger = logging.getLogger(__name__)


class GenerateAdminTechLogError(CatalystwanException):
    pass


class DownloadAdminTechLogError(CatalystwanException):
    pass


class RequestTokenIdNotFound(CatalystwanException):
    pass


class AdminTechAPI:
    """Class for handling admintech logs for a device.

    Attributes:
        session: logged in API client session

    Usage example:
        # Create session
        session = create_manager_session(...)
        # Get admintech data for all devices
        all_admintechs = session.api.admin_tech.get_all()
    """

    def __init__(self, session: ManagerSession) -> None:
        self.session = session

    def __str__(self) -> str:
        return str(self.session)

    def get(self, device_id: str) -> List[DeviceAdminTech]:
        """Gets admintech log information for a device.

        Args:
            device_id: device ID (usually system-ip)
        Returns:
            AdminTech object list for given device
        """
        body = {"deviceIP": device_id}
        response = self.session.post(url="/dataservice/device/tools/admintechlist", json=body)
        items = response.json()["data"]
        return [create_dataclass(DeviceAdminTech, item) for item in items]

    def get_all(self) -> List[AdminTech]:
        """Gets admintech log information for all devices.

        Returns:
            AdminTech objects list for all devices
        """
        response = self.session.get("/dataservice/device/tools/admintechs")
        items = response.json()["data"]
        return [create_dataclass(AdminTech, item) for item in items]

    def generate(
        self,
        device_id: str,
        exclude_cores: bool = True,
        exclude_tech: bool = False,
        exclude_logs: bool = True,
        request_timeout: int = 3600,
        polling_timeout: int = 1200,
        polling_interval: int = 30,
    ) -> str:
        """Generates admintech log for a device.
        Args:
            device_id: device ID (usually system-ip)
            exclude_cores: exclude core in generated admintech log file
            exclude_tech: exclude tech in generated admintech log file
            exclude_logs: exclude logs in generated admintech log file
            request_timeout: wait time in seconds to generate admintech after request
            polling_timeout: retry period in seconds for successfull request
            polling_interval: polling interval in seconds between request attempts
        Returns:
            filename of generated admintech log
        """
        create_admin_tech_error_msgs = "Admin tech creation already in progress"
        body = {
            "deviceIP": device_id,
            "exclude-cores": exclude_cores,
            "exclude-tech": exclude_tech,
            "exclude-logs": exclude_logs,
        }
        polling_timer = polling_timeout
        while polling_timer > 0:
            logger.info(
                f"Starting AdminTech log creation for {device_id}, waiting up to {request_timeout} seconds to complete"
            )
            try:
                response = self.session.post(
                    url="/dataservice/device/tools/admintech",
                    json=body,
                    timeout=request_timeout,
                )
            except ManagerHTTPError as http_error:
                response = http_error.response  # type: ignore
            if response.status_code == 200:
                return response.json()["fileName"]
            if response.status_code == 400 and create_admin_tech_error_msgs in response.json().get("error", {}).get(
                "details", ""
            ):
                logger.warning(f"Admin tech creation already in progress, retrying in {polling_interval} seconds")
            else:
                raise GenerateAdminTechLogError(f"It is not possible to generate admintech log for {device_id}")
            time.sleep(polling_interval)
            polling_timer -= polling_interval
        raise GenerateAdminTechLogError(f"It is not possible to generate admintech log for {device_id}")

    def _get_token_id(self, filename: str, timeout: int, interval: int) -> str:
        # Wait for the file to be ready and obtain the token_id
        end_time = time.time() + timeout
        while time.time() < end_time:
            admin_techs = self.get_all()
            for admin_tech in admin_techs:
                if filename == admin_tech.filename and admin_tech.state == "done":
                    return admin_tech.token_id
            time.sleep(interval)
        raise RequestTokenIdNotFound(
            f"requestTokenId of admin tech generation request not found for file name: {filename}"
        )

    def delete(self, filename: str, timeout: int = 3600, interval: int = 30) -> ManagerResponse:
        """Deletes admin tech logs for a device.
        Args:
            filename: name of admin_tech file
        Returns:
            response: http response for delete operation
        """

        token_id = self._get_token_id(filename, timeout, interval)
        response = self.session.delete(f"/dataservice/device/tools/admintech/{token_id}")
        if response.status_code == 200:
            logger.info(f"Deleted AdminTech file {filename} on remote")
        return response

    def download(self, filename: str, download_dir: Optional[Path] = None) -> Path:
        """Downloads admintech log for a device.
        Args:
            filename: name of admin_tech file
            download_dir: download directory (defaults to current working directory)
        Returns:
            path to downloaded admin_tech file
        """
        if not download_dir:
            download_dir = Path.cwd()
        download_path = download_dir / filename
        url = f"/dataservice/device/tools/admintech/download/{filename}"
        if self.session.get_file(url=url, filename=download_path).status_code != 200:
            raise DownloadAdminTechLogError(f"Cannot download admin tech file: {filename} from remote")
        logger.info(f"Downloaded AdminTech file to: {download_path}")
        return download_path
