"""
Sarmat.
Ядро пакета.
Описание бизнес логики.
Вспомогательные инструменты.
Инструменты для построения расписания.
"""
from datetime import date, datetime, timedelta
from itertools import cycle
from uuid import uuid4

from sarmat.core.constants import DurationMonthCalcStrategy, SarmatWrongValueError
from sarmat.core.context.models import (
    CalendarPublication,
    IntervalModel,
    JourneyModel,
    JourneyPublication,
    RoutePublication,
)
from sarmat.core.context.tools.calculators.interval import get_datetime_sequence_from_interval_item
from sarmat.core.context.tools.calculators.price import PriceCalculator


def make_publication_from_journey(journey: JourneyModel, owner: int) -> JourneyPublication:
    """Создание объекта публикации рейса для станционного расписания.

    Args:
        journey: рейс
        owner: владелец публикации

    Returns: публикация
    """
    if not journey.departure_station:
        raise SarmatWrongValueError(description="Для рейса не указана станция отправления")

    if not journey.number:
        raise SarmatWrongValueError(description="Не указан номер рейса")

    uid = uuid4()
    route = [
        RoutePublication(
            journey=uid,
            sequence_number=0,
            point_id=journey.start_point.id,
            point_name=journey.start_point.name,
            station_id=journey.departure_station.id,
            station_name=journey.departure_station.name,
            departure_time=journey.departure_time,
        ),
    ]
    dt = datetime.combine(date.today(), journey.departure_time)

    for idx, item in enumerate(journey.structure):
        dt += timedelta(minutes=item.travel_time_min)
        arr_time = dt.time()

        if item.stop_time_min:
            dt += timedelta(minutes=item.stop_time_min)

        dep_time = dt.time()

        route.append(
            RoutePublication(
                journey=uid,
                sequence_number=idx+1,
                point_id=item.point.id,
                point_name=item.point.name,
                station_id=item.station.id if item.station else None,
                station_name=item.station.name if item.station else None,
                arrive_time=arr_time,
                departure_time=dep_time,
                stop_time=item.stop_time_min,
            )
        )

    return JourneyPublication(
        uid=uid,
        owner=owner,
        journey_type=journey.journey_type,
        name=journey.name,
        number=journey.number,
        literal=journey.literal,
        departure_time=journey.departure_time,
        is_chartered=journey.is_chartered,
        need_control=journey.need_control,
        route=route,
        calendar=[],
    )


def get_fare_matrix_for_journey(
    journey: JourneyModel,
    calc: PriceCalculator,
    sparce: bool = True
) -> list[list[float]]:
    """Вычисление матрицы стоимости проезда.

    Args:
        journey: рейс
        calc: правила вычисления стоимости
        sparce: признак для получения разреженных значений,
                когда каждая строка содержит полный набор значений,
                но для предыдущих пунктов эти значения равны 0

    Returns: последовательность значений для каждого пункта маршрута
    """
    route_len = len(journey.structure) + 1
    if sparce:
        fare = [[0.0 for _ in range(route_len)] for _ in range(route_len)]
    else:
        fare = []

    for i in range(route_len):
        if not sparce:
            fare.append([])

        for j in range(i + 1, route_len):
            val = calc.calculate_price(
                journey,
                departure_index=i,
                destination_index=j,
            )
            if sparce:
                fare[i][j] = round(val, 2)
            else:
                fare[i].append(round(val, 2))
    return fare


def fill_journey_price(
    publication: JourneyPublication,
    journey: JourneyModel,
    p_calculator: PriceCalculator,
    b_calculator: PriceCalculator,
) -> JourneyPublication:
    """Заполнение публикации сведениями о стоимости проезда

    Args:
        publication: публикуемое расписание для рейса
        journey: рейс
        p_calculator: правила расчёта стоимости проезда
        b_calculator: правила расчёта стоимости провоза багажа

    Returns: публикуемое расписание для рейса
    """
    pas_fare = get_fare_matrix_for_journey(journey, p_calculator, sparce=False)
    bag_fare = get_fare_matrix_for_journey(journey, b_calculator, sparce=False)

    for idx, r in enumerate(publication.route):
        r.ticket_amount = pas_fare[idx]
        r.baggage_amount = bag_fare[idx]

    return publication


def fill_journey_calendar(
    publication: JourneyPublication,
    journey: JourneyModel,
    interval: IntervalModel,
    operators: list[int],
    season_start: date,
    season_stop: date,
    calc_strategy: DurationMonthCalcStrategy,
    filter_by: int | None = None,
) -> JourneyPublication:
    """Заполнение графика выезда в рейс.

    Args:
        publication: публикация рейса
        journey: рейс
        interval: интервал выезда
        operators: перевозчики, обслуживающие рейс
        season_start: начало действия
        season_stop: окончание действия
        calc_strategy: стратегия расчёта времени для месяцев
        filter_by: формирование расписание по одному
                   из перевозчиков с учётом очерёдности обслуживания

    Returns: публикация рейса
    """
    operators_ordering = cycle(operators)
    if not interval.values:
        raise SarmatWrongValueError(description="Для интервала не указаны значения")

    datetime_sequence = get_datetime_sequence_from_interval_item(
        interval.values[0],
        datetime.combine(season_start, journey.departure_time),
        datetime.combine(season_stop, journey.departure_time),
        calc_strategy,
    )
    for i in datetime_sequence:
        operator = next(operators_ordering)
        if filter_by and operator != filter_by:
            continue

        publication.calendar.append(
            CalendarPublication(
                journey=publication.uid,
                carrier=operator,
                departure_date=i.date(),
            )
        )
    return publication
