"""Module for sending operations"""

import logging
import time
from datetime import datetime
from urllib.parse import urlencode

import requests

from ontu_parser.classes.js_parser import CookieValues, JavaScriptParser

from .base import BaseClass
from .enums import RequestsEnum

API_URL = "https://rozklad.ontu.edu.ua"
DEFAULT_HEADERS = {
    # Why not? I like to show off :)
    "User-Agent": "makisukurisu/rozklad-ontu-parser; (ontu_schedule_bot)",
}


class TTLValue(BaseClass):
    """Describes value with some time to live (like authorization token)"""

    _ttl: int = 3600  # Time To Live (in seconds)
    _value: object = None

    issued_at: datetime = datetime.min

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.issued_at = datetime.now()

    def is_valid(self):
        """
        Checks wether value is still valid
            True if TTLValue was issued less seconds before than Time To Live
        """
        seconds_passed = (datetime.now() - self.issued_at).seconds
        if seconds_passed < self._ttl:
            return True
        return False

    def set_value(self, value):
        """Sets value, and resets issued_at"""
        self.issued_at = datetime.now()
        self._value = value
        return self._value


class Cookies(TTLValue):
    """Describes cookies (Temporary values) for requests"""

    def __init__(self, sender, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.sender: Sender = sender

    @property
    def value(self) -> dict[str, str]:
        """Returns value of a cookie (or gets one, if not present)"""
        if self._value and self.is_valid():
            return self._value

        logging.warning("Cookies are being updated")
        cookie = self.get_cookie()
        if not cookie:
            raise RuntimeError("Could not get cookies")
        return cookie

    def _try_get_cookie(self) -> dict[str, str]:
        with requests.session() as session:
            response = session.get(
                API_URL,
                headers=DEFAULT_HEADERS,
            )

            if response.status_code != RequestsEnum.Codes.SERVICE_UNAVAILABLE.value:
                logging.error("Expected to get a 503 response. Returning old value")
                return self._value

            cookies = JavaScriptParser(
                html=response.text,
            ).parse()

            session_response = session.get(
                API_URL,
                headers=DEFAULT_HEADERS,
                cookies=CookieValues.to_dict(cookies),
            )
            php_session_id = session_response.cookies.get("PHPSESSID")
            if (
                session_response.status_code
                == RequestsEnum.Codes.SERVICE_UNAVAILABLE.value
            ) or php_session_id is None:
                raise ValueError("Could not get cookies")

            cookies["PHPSESSID"] = php_session_id
            return self.set_value(cookies)

    def get_cookie(self):
        """
        Sends a request to a server. If we get response with status code 503,
        we know that we need to generate cookies.

        Cookies (at the moment) are generated by executing a
        JavaScript that's embedded in the 503 response.
        """
        # pylint: disable=logging-fstring-interpolation
        for attempt in range(1, 4):
            try:
                return self._try_get_cookie()
            except ValueError as exception:
                delay = attempt * attempt
                logging.error(
                    f"Could not get cookies, got exception: {exception}.",
                )
                logging.warning(
                    f"Retrying in {delay} seconds",
                )
                time.sleep(delay)
        raise RuntimeError("Could not get cookies after 3 attempts")


class Sender(BaseClass):
    """Describes sender with link and cookies to send requests"""

    default_link: str = f"{API_URL}/guest_n.php"
    teachers_link: str = f"{API_URL}/departments_all.php"
    cookies: Cookies = None
    for_teachers: bool = False

    @property
    def link(self):
        """Returns link to send requests to"""
        if self.for_teachers:
            return self.teachers_link
        return self.default_link

    def __init__(self, *args, **kwargs):
        for_teachers = kwargs.get("for_teachers", False)
        self.for_teachers = for_teachers
        self.cookies = Cookies(self)

    _responses: list[requests.Response] = []

    def send_request(
        self,
        method: str,
        data: (dict | None) = None,
        query: (dict | None) = None,
    ):
        """Sends request with method and some data, if needed"""
        session = requests.Session()
        if method not in RequestsEnum.Methods.choices():
            raise ValueError(
                f"arg. `method` should be one of: {RequestsEnum.Methods.choices()}",
                method,
            )

        link = self.link
        if query:
            link += "?" + urlencode(query)

        try:
            response: requests.Response = session.request(
                method=method,
                url=link,
                data=data,
                headers=DEFAULT_HEADERS,
                cookies=self.cookies.value,
            )
        except Exception as exception:
            raise ValueError(
                f"could not get response from {link}, got exception: {exception}"
            ) from exception
        if response.status_code != RequestsEnum.code_ok():
            raise ValueError(
                "server returned non OK response",
                response.status_code,
                response,
                response.content,
            )
        # Keep responses for a little while
        self._responses.append(response)
        self._responses = self._responses[-5:]

        return response
