from __future__ import annotations

import enum
import pprint
import typing as t
from collections.abc import Sequence
from dataclasses import dataclass, field

import pandas as pd
from dopt_basics.datastructures import DualDict
from pydantic import BaseModel, ConfigDict, SkipValidation

__all__ = ["DualDict"]

# ** Pipeline state management
StatusDescription: t.TypeAlias = tuple[str, int, str]
R = t.TypeVar("R", bound="ExportResponse")
S = t.TypeVar("S", bound="Statistics")


class IError(t.Protocol):
    code: int
    description: str
    message: str


class Status(BaseModel):
    code: SkipValidation[int]
    description: SkipValidation[str]
    message: SkipValidation[str] = ""
    api_server_error: SkipValidation[DelBarApiError | None] = None

    def __str__(self) -> str:
        py_repr = self.model_dump()
        return pprint.pformat(py_repr, indent=4, sort_dicts=False)


class ResponseType(BaseModel):
    pass


class ExportResponse(BaseModel):
    response: ResponseType
    status: Status


@dataclass(slots=True)
class DataPipeStates:
    SUCCESS: Status
    CONNECTION_TIMEOUT: Status
    CONNECTION_ERROR: Status
    TOO_FEW_POINTS: Status
    TOO_FEW_MONTH_POINTS: Status
    NO_RELIABLE_FORECAST: Status


@dataclass(slots=True)
class PipeResult(t.Generic[R, S]):
    data: pd.DataFrame | None
    status: Status
    results: R | None = None
    statistics: S | None = None

    def success(
        self,
        data: pd.DataFrame,
        status: Status,
    ) -> None:
        self.data = data
        self.status = status
        self.results = None

    def fail(
        self,
        status: Status,
    ) -> None:
        self.data = None
        self.status = status
        self.results = None

    def export(
        self,
        response: R,
    ) -> None:
        self.data = None
        self.status = response.status
        self.results = response

    def stats(
        self,
        statistics: S,
    ) -> None:
        self.statistics = statistics


JsonExportResponse = t.NewType("JsonExportResponse", str)
JsonResponse = t.NewType("JsonResponse", str)
JsonStatus = t.NewType("JsonStatus", str)


# ** API
class ApiCredentials(BaseModel):
    model_config: ConfigDict = ConfigDict(str_strip_whitespace=True)

    username: str
    password: str
    database: str
    mandant: str


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


class HttpRequestTypes(enum.StrEnum):
    GET = enum.auto()
    POST = enum.auto()
    PUT = enum.auto()
    DELETE = enum.auto()


HttpContentHeaders = t.TypedDict(
    "HttpContentHeaders",
    {
        "Content-type": str,
        "Accept": str,
        "DelecoToken": t.NotRequired[str],
    },
)


# ** statistics
class Statistics:
    pass


# ** ---- performance
class PipelineMetrics(t.TypedDict):
    pipeline_name: str
    execution_duration: float


# ** ---- forecasts
@dataclass(slots=True)
class CustomerDataSalesForecast:
    order: list[int] = field(default_factory=list)
    date: list[pd.Timestamp] = field(default_factory=list)
    sales: list[float] = field(default_factory=list)


class ParamSearchXGBRegressor(t.TypedDict):
    n_estimators: t.Any
    learning_rate: Sequence[float]
    max_depth: range
    min_child_weight: range
    gamma: Sequence[float]
    subsample: Sequence[float]
    colsample_bytree: Sequence[float]
    early_stopping_rounds: Sequence[int]


@dataclass(slots=True, eq=False)
class SalesForecastStatistics(Statistics):
    status_code: int
    status_dscr: str
    length_dataset: int
    score_mae: float | None = None
    score_r2: float | None = None
    best_start_year: int | None = None
    xgb_params: BestParametersXGBRegressor | None = None


class BestParametersXGBRegressor(t.TypedDict):
    forecast_id: t.NotRequired[int]
    n_estimators: int
    learning_rate: float
    max_depth: int
    min_child_weight: int
    gamma: float
    subsample: float
    colsample_bytree: float
    early_stopping_rounds: int
