from __future__ import annotations

import re
from datetime import datetime as Datetime
from typing import Final, Never

import requests
from pydantic import BaseModel, PositiveInt
from requests import Response

from delta_barth.constants import HTTP_CURRENT_CONNECTION, KnownApiErrorCodes
from delta_barth.errors import (
    ApiConnectionError,
    UnknownApiErrorCode,
    UnspecifiedRequestType,
)
from delta_barth.types import HttpRequestTypes

LOGIN_ERROR_CODES_KNOWN: Final[frozenset[int]] = frozenset((400, 401, 409, 500))


class DelBarApiError(BaseModel):
    status_code: int
    message: str = ""
    code: str | None = None
    hints: str | None = None
    type: str | None = None
    title: str | None = None
    errors: dict | None = None
    traceId: str | None = None


def _raise_for_unknown_error(
    resp: Response,
) -> Never:
    raise UnknownApiErrorCode(
        f"Unknown response code for request. Status Code: {resp.status_code}, "
        f"Content: {resp.text}"
    )


def _assert_login_status() -> None:
    if not HTTP_CURRENT_CONNECTION.logged_in:
        raise ApiConnectionError("Curent session is not logged in")


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
    error: DelBarApiError | None = None


def login(
    base_url: str,
    user_name: str,
    password: str,
    database: str,
    mandant: str,
) -> LoginResponse:
    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=HTTP_CURRENT_CONNECTION.headers,  # type: ignore
    )

    response: LoginResponse
    if resp.status_code == 200:
        response = LoginResponse(**resp.json())
        HTTP_CURRENT_CONNECTION.add_session_token(response.token)
    elif resp.status_code in KnownApiErrorCodes.COMMON.value:
        err = DelBarApiError(status_code=resp.status_code, **resp.json())
        response = LoginResponse(token="", error=err)
    else:  # pragma: no cover
        _raise_for_unknown_error(resp)

    return response


# ** logout
class LogoutResponse(BaseModel):
    error: DelBarApiError | None = None


def logout(
    base_url: str,
) -> LogoutResponse:
    ROUTE: Final[str] = "user/logout"
    URL: Final = combine_route(base_url, ROUTE)

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

    response: LogoutResponse
    if resp.status_code == 200:
        response = LogoutResponse()
        HTTP_CURRENT_CONNECTION.remove_session_token()
    elif resp.status_code in KnownApiErrorCodes.COMMON.value:
        err = DelBarApiError(status_code=resp.status_code, **resp.json())
        response = LogoutResponse(error=err)
    else:  # pragma: no cover
        _raise_for_unknown_error(resp)

    return response


# ** sales data
class SalesPrognosisRequestP(BaseModel):
    FirmaId: int | None
    BuchungsDatum: Datetime | None


class SalesPrognosisResponseEntry(BaseModel):
    artikelId: PositiveInt
    firmaId: PositiveInt
    betrag: float  # negative values are filtered out later
    menge: float  # reasons for negative values unknown
    buchungsDatum: Datetime


class SalesPrognosisResponse(BaseModel):
    daten: tuple[SalesPrognosisResponseEntry, ...]
    error: DelBarApiError | None = None


def get_sales_prognosis_data(
    base_url: str,
    company_id: int | None = None,
    start_date: Datetime | None = None,
) -> SalesPrognosisResponse:
    _assert_login_status()
    ROUTE: Final[str] = "verkauf/umsatzprognosedaten"
    URL: Final = combine_route(base_url, ROUTE)

    sales_prog_req = SalesPrognosisRequestP(
        FirmaId=company_id,
        BuchungsDatum=start_date,
    )
    resp = requests.get(
        URL,
        params=sales_prog_req.model_dump(mode="json", exclude_none=True),
        headers=HTTP_CURRENT_CONNECTION.headers,  # type: ignore[argumentType]
    )

    response: SalesPrognosisResponse
    if resp.status_code == 200:
        response = SalesPrognosisResponse(**resp.json())
    elif resp.status_code in KnownApiErrorCodes.COMMON.value:  # pragma: no cover
        err = DelBarApiError(status_code=resp.status_code, **resp.json())
        response = SalesPrognosisResponse(daten=tuple(), error=err)
    else:  # pragma: no cover
        _raise_for_unknown_error(resp)

    return response
