from datetime import datetime
from typing import List, Optional
import pytest

from backend.app.domain.classes.plan import Plan
from backend.app.domain.classes.scenario import Scenario
from backend.app.domain.records.plan_record import PlanRecord
from backend.app.domain.records.scenario_record import ScenarioRecord
from backend.app.db.models.portfolio import PortfolioGoal
from backend.categorisation.processor import choose_cheapest_relevant_plan
from datatypes.epc.domain.epc import Epc


@pytest.fixture
def created_at_datetime() -> datetime:
    return datetime.now()


def make_plan_record(
    created_at: datetime, default: bool, cost_of_works: Optional[float] = 500.0
) -> PlanRecord:
    return PlanRecord(
        property_id=1,
        portfolio_id=1,
        created_at=created_at,
        is_default=default,
        post_epc_rating=Epc.C,
        cost_of_works=cost_of_works,
    )


def make_scenario(name: str, created_at: datetime, is_default: bool) -> Scenario:
    record = ScenarioRecord(
        name=name,
        created_at=created_at,
        housing_type="",
        goal=PortfolioGoal.INCREASING_EPC,
        goal_value="C",
        trigger_file_path="",
        multi_plan=False,
        is_default=is_default,
    )
    return Scenario(record=record, id=3 if is_default else 4)


def make_plan(
    created_at: datetime,
    default: bool,
    cost_of_works: Optional[float] = 500.0,
    name: str = "",
) -> Plan:
    scenario = make_scenario(name, created_at, default)
    plan_id = 1 if default else 2
    return Plan(
        record=make_plan_record(created_at, default, cost_of_works),
        scenario=scenario,
        id=plan_id,
    )


def test_prioritised_scenario_selected(created_at_datetime: datetime) -> None:
    # arrange
    epc_c_plan = make_plan(created_at_datetime, True, name="EPC C")
    minor_works_plan = make_plan(created_at_datetime, False, name="EPC C - Minor Works")
    scenario_priority_order: List[int] = [4, 3]
    expected_default_plan_id = 2

    # act
    actual_default_plan = choose_cheapest_relevant_plan(
        plans=[epc_c_plan, minor_works_plan],
        scenario_priority_order=scenario_priority_order,
    )

    # assert
    assert actual_default_plan.id == expected_default_plan_id


def test_cheapest_plan_returned_if_not_in_priority_list(
    created_at_datetime: datetime,
) -> None:
    # arrange
    epc_c_plan = make_plan(
        created_at_datetime, True, cost_of_works=1000.0, name="EPC C"
    )
    minor_works_plan = make_plan(
        created_at_datetime, False, cost_of_works=100.0, name="EPC C - Minor Works"
    )
    scenario_priority_order: List[int] = [3, 5]
    expected_default_plan_id = 2

    # act
    actual_default_plan = choose_cheapest_relevant_plan(
        plans=[epc_c_plan, minor_works_plan],
        scenario_priority_order=scenario_priority_order,
    )

    # assert
    assert actual_default_plan.id == expected_default_plan_id


def test_all_plans_zero_cost__highest_priority_returned(
    created_at_datetime: datetime,
) -> None:
    # arrange
    epc_c_plan = make_plan(created_at_datetime, True, cost_of_works=0.0, name="EPC C")
    minor_works_plan = make_plan(
        created_at_datetime, False, cost_of_works=0.0, name="EPC C - Minor Works"
    )
    scenario_priority_order: List[int] = [4, 3]
    expected_default_plan_id = 2

    # act
    actual_default_plan = choose_cheapest_relevant_plan(
        plans=[epc_c_plan, minor_works_plan],
        scenario_priority_order=scenario_priority_order,
    )

    # assert
    assert actual_default_plan.id == expected_default_plan_id


def test_some_plans_zero_cost__cheapest_returned(
    created_at_datetime: datetime,
) -> None:
    # arrange
    epc_c_plan = make_plan(created_at_datetime, True, cost_of_works=0.0, name="EPC C")
    minor_works_plan = make_plan(
        created_at_datetime, False, cost_of_works=50.0, name="EPC C - Minor Works"
    )
    scenario_priority_order: List[int] = [4, 3]
    expected_default_plan_id = 1

    # act
    actual_default_plan = choose_cheapest_relevant_plan(
        plans=[epc_c_plan, minor_works_plan],
        scenario_priority_order=scenario_priority_order,
    )

    # assert
    assert actual_default_plan.id == expected_default_plan_id


def test_all_plans_null_cost__highest_priority_returned(
    created_at_datetime: datetime,
) -> None:
    # arrange
    epc_c_plan = make_plan(created_at_datetime, True, cost_of_works=None, name="EPC C")
    minor_works_plan = make_plan(
        created_at_datetime, False, cost_of_works=None, name="EPC C - Minor Works"
    )
    scenario_priority_order: List[int] = [4, 3]
    expected_default_plan_id = 2

    # act
    actual_default_plan = choose_cheapest_relevant_plan(
        plans=[epc_c_plan, minor_works_plan],
        scenario_priority_order=scenario_priority_order,
    )

    # assert
    assert actual_default_plan.id == expected_default_plan_id
