from __future__ import annotations

import re
from typing import TYPE_CHECKING, Final

import requests
from pydantic import BaseModel
from requests import Response

from delta_barth.errors import (
    STATUS_HANDLER,
    UnspecifiedRequestType,
)
from delta_barth.types import (
    ApiCredentials,
    DelBarApiError,
    HttpRequestTypes,
)

if TYPE_CHECKING:
    from delta_barth.types import HttpContentHeaders, Status


class Session:
    def __init__(
        self,
        base_headers: HttpContentHeaders,
    ) -> None:
        self._creds: ApiCredentials | None = None
        self._base_url: str | None = None
        self._headers = base_headers
        self._session_token: str | None = None
        self._logged_in: bool = False

    @property
    def creds(self) -> ApiCredentials:
        assert self._creds is not None, "accessed credentials not set"
        return self._creds

    def set_credentials(
        self,
        user_name: str,
        password: str,
        database: str,
        mandant: str,
    ) -> None:
        self._creds = validate_credentials(
            user_name=user_name,
            password=password,
            database=database,
            mandant=mandant,
        )

    @property
    def base_url(self) -> str:
        assert self._base_url is not None, "accessed base URL not set"
        return self._base_url

    def set_base_url(
        self,
        base_url: str,
    ) -> None:
        self._base_url = base_url

    @property
    def headers(self) -> HttpContentHeaders:
        return self._headers

    @property
    def session_token(self) -> str | None:
        return self._session_token

    @property
    def logged_in(self) -> bool:
        return self._logged_in

    def _add_session_token(
        self,
        token: str,
    ) -> None:
        assert self.session_token is None, "tried overwriting existing API session token"
        self._session_token = token
        self._headers.update(DelecoToken=token)
        self._logged_in = True

    def _remove_session_token(self) -> None:
        assert self.session_token is not None, (
            "tried to delete non-existing API session token"
        )
        if "DelecoToken" in self.headers:
            del self._headers["DelecoToken"]
            self._session_token = None
            self._logged_in = False

    def login(
        self,
    ) -> tuple[LoginResponse, Status]:
        ROUTE: Final[str] = "user/login"
        URL: Final = combine_route(self.base_url, ROUTE)

        login_req = LoginRequest(
            userName=self.creds.user_name,
            password=self.creds.password,
            databaseName=self.creds.database,
            mandantName=self.creds.mandant,
        )
        resp = requests.put(
            URL,
            login_req.model_dump_json(),
            headers=self.headers,  # type: ignore
        )

        response: LoginResponse
        status: Status
        if resp.status_code == 200:
            response = LoginResponse(**resp.json())
            status = STATUS_HANDLER.pipe_states.SUCCESS
            self._add_session_token(response.token)
        else:
            response = LoginResponse(token="")
            err = DelBarApiError(status_code=resp.status_code, **resp.json())
            status = STATUS_HANDLER.api_error(err)

        return response, status

    def logout(
        self,
    ) -> tuple[None, Status]:
        ROUTE: Final[str] = "user/logout"
        URL: Final = combine_route(self.base_url, ROUTE)

        resp = requests.put(
            URL,
            headers=self.headers,  # type: ignore
        )

        response = None
        status: Status
        if resp.status_code == 200:
            status = STATUS_HANDLER.SUCCESS
            self._remove_session_token()
        else:
            err = DelBarApiError(status_code=resp.status_code, **resp.json())
            status = STATUS_HANDLER.api_error(err)

        return response, status

    def assert_login(
        self,
    ) -> tuple[LoginResponse, Status]:
        # check if login token is still valid
        # re-login if necessary
        if self.session_token is None:
            return self.login()

        # use known endpoint which requires a valid token in its header
        # evaluate the response to decide if:
        # current token is still valid, token is not valid, other errors occurred
        ROUTE: Final[str] = "verkauf/umsatzprognosedaten"
        URL: Final = combine_route(self.base_url, ROUTE)
        params: dict[str, int] = {"FirmaId": 999999}
        resp = requests.get(
            URL,
            params=params,
            headers=self.headers,  # type: ignore
        )

        response: LoginResponse
        status: Status
        if resp.status_code == 200:
            # TODO use to check for catching unknown exceptions
            # response = LoginResponse(**resp.json())
            response = LoginResponse(token=self.session_token)
            status = STATUS_HANDLER.SUCCESS
        elif resp.status_code == 401:
            self._remove_session_token()
            response, status = self.login()
        else:  # pragma: no cover
            response = LoginResponse(token="")
            err = DelBarApiError(status_code=resp.status_code, **resp.json())
            status = STATUS_HANDLER.api_error(err)

        return response, status


def validate_credentials(
    user_name: str,
    password: str,
    database: str,
    mandant: str,
) -> ApiCredentials:
    return ApiCredentials(
        user_name=user_name,
        password=password,
        database=database,
        mandant=mandant,
    )


# def _assert_login_status() -> None:
#     if not CURRENT_SESSION.logged_in:
#         raise ApiConnectionError("Curent session is not logged in")
# assert login:
# doing request to defined end point which


def _strip_url_components(string: str) -> str:
    return re.sub(r"^[ /]+|[ /]+$", "", string)


def combine_route(base_url: str, route: str) -> str:
    base_url = _strip_url_components(base_url)
    route = _strip_url_components(route)
    return "/".join((base_url, route))


def ping(
    base_url: str,
    method: HttpRequestTypes,
) -> Response:
    ROUTE: Final[str] = "ping"
    URL: Final = combine_route(base_url, ROUTE)

    resp: Response
    if method == HttpRequestTypes.GET:
        resp = requests.get(URL)
    elif method == HttpRequestTypes.PUT:
        resp = requests.put(URL)
    elif method == HttpRequestTypes.DELETE:
        resp = requests.delete(URL)
    else:
        raise UnspecifiedRequestType(f"Request type {method} not defined for endpoint")

    return resp


# ** login
class LoginRequest(BaseModel):
    userName: str
    password: str
    databaseName: str
    mandantName: str


class LoginResponse(BaseModel):
    token: str


# def login(
#     base_url: str,
#     user_name: str,
#     password: str,
#     database: str,
#     mandant: str,
# ) -> tuple[LoginResponse, Status]:
#     ROUTE: Final[str] = "user/login"
#     URL: Final = combine_route(base_url, ROUTE)

#     login_req = LoginRequest(
#         userName=user_name,
#         password=password,
#         databaseName=database,
#         mandantName=mandant,
#     )
#     resp = requests.put(
#         URL,
#         login_req.model_dump_json(),
#         headers=CURRENT_SESSION.headers,  # type: ignore
#     )

#     response: LoginResponse
#     status: Status
#     if resp.status_code == 200:
#         response = LoginResponse(**resp.json())
#         status = STATE_HANDLER.pipe_states.SUCCESS
#         CURRENT_SESSION.add_session_token(response.token)
#     else:
#         response = LoginResponse(token="")
#         err = DelBarApiError(status_code=resp.status_code, **resp.json())
#         status = STATE_HANDLER.api_error(err)

#     return response, status


# ** logout
# def logout(
#     base_url: str,
# ) -> tuple[None, Status]:
#     ROUTE: Final[str] = "user/logout"
#     URL: Final = combine_route(base_url, ROUTE)

#     resp = requests.put(
#         URL,
#         headers=CURRENT_SESSION.headers,  # type: ignore
#     )

#     response = None
#     status: Status
#     if resp.status_code == 200:
#         status = STATE_HANDLER.SUCCESS
#         CURRENT_SESSION.remove_session_token()
#     else:
#         err = DelBarApiError(status_code=resp.status_code, **resp.json())
#         status = STATE_HANDLER.api_error(err)

#     return response, status
