from abc import abstractmethod
from datetime import datetime
from decimal import Decimal
from typing import List, Optional, Set

from maps_adv.adv_store.v2.lib.data_managers import sqls
from maps_adv.adv_store.v2.lib.data_managers.base import BaseDataManager
from maps_adv.adv_store.v2.lib.db import db_enum_converter
from maps_adv.adv_store.api.schemas.enums import CampaignEventTypeEnum


class BaseEventsDataManager(BaseDataManager):
    __slots__ = ()

    @abstractmethod
    async def create_event(
        self,
        timestamp: datetime,
        campaign_id: int,
        event_type: CampaignEventTypeEnum,
        event_data: str,
    ) -> int:
        raise NotImplementedError()

    @abstractmethod
    async def create_events_for_campaigns(
        self,
        timestamp: datetime,
        campaign_ids: Set[int],
        event_type: CampaignEventTypeEnum,
        event_data: str,
    ) -> None:
        raise NotImplementedError()

    @abstractmethod
    async def retrieve_campaigns_events_by_orders(
        self,
        order_ids: Optional[List[int]] = None,
        manul_order_ids: Optional[List[int]] = None,
        from_timestamp: Optional[datetime] = None,
        to_timestamp: Optional[datetime] = None,
        starting_event_id: Optional[int] = None,
        limit: Optional[int] = 20,
    ) -> List[dict]:
        raise NotImplementedError()

    @abstractmethod
    async def create_events_stopped_budget_reached(
        self, campaign_ids: Set[int], timestamp: Optional[datetime] = None
    ) -> int:
        raise NotImplementedError()

    @abstractmethod
    async def create_event_stopped_manually(
        self,
        campaign_id: int,
        initiator_id: int,
        metadata: str,
        timestamp: Optional[datetime] = None,
    ) -> int:
        raise NotImplementedError()

    @abstractmethod
    async def create_event_end_datetime_changed(
        self,
        campaign_id: int,
        initiator_id: int,
        prev_datetime: datetime,
        timestamp: Optional[datetime] = None,
    ) -> int:
        raise NotImplementedError()

    @abstractmethod
    async def create_event_budget_decreased(
        self,
        campaign_id: int,
        initiator_id: int,
        prev_budget: Decimal,
        timestamp: Optional[datetime] = None,
    ) -> int:
        raise NotImplementedError()


class EventsDataManager(BaseEventsDataManager):
    __slots__ = ()

    async def create_event(
        self,
        timestamp: datetime,
        campaign_id: int,
        event_type: CampaignEventTypeEnum,
        event_data: str,
    ) -> int:

        async with self.connection() as con:
            event_id = await con.fetchval(
                sqls.create_campaign_event,
                timestamp,
                campaign_id,
                db_enum_converter.from_enum(event_type),
                event_data,
            )
            return event_id

    async def create_events_for_campaigns(
        self,
        timestamp: datetime,
        campaign_ids: List[int],
        event_type: CampaignEventTypeEnum,
        event_data: str,
    ) -> None:

        event_type = db_enum_converter.from_enum(event_type)

        async with self.connection() as con:
            values = list()
            for campaign_id in campaign_ids:
                values.append((timestamp, campaign_id, event_type, event_data))

            await con.executemany(sqls.create_campaign_event, values)

    async def retrieve_campaigns_events_by_orders(
        self,
        order_ids: Optional[List[int]] = None,
        manul_order_ids: Optional[List[int]] = None,
        from_timestamp: Optional[datetime] = None,
        to_timestamp: Optional[datetime] = None,
        starting_event_id: Optional[int] = None,
        limit: Optional[int] = 20,
    ) -> List[dict]:
        async with self.connection() as con:
            rows = await con.fetch(
                sqls.retrieve_campaigns_events_by_orders,
                order_ids,
                manul_order_ids,
                from_timestamp,
                to_timestamp,
                starting_event_id,
                limit,
            )

        result = [dict(row) for row in rows]

        for event in result:
            event["event_type"] = db_enum_converter.to_enum(
                CampaignEventTypeEnum, event["event_type"]
            )

        return result

    async def create_events_stopped_budget_reached(
        self, campaign_ids: Set[int], timestamp: Optional[datetime] = None
    ) -> None:

        timestamp = timestamp or datetime.now()

        await self.create_events_for_campaigns(
            timestamp=timestamp,
            campaign_ids=campaign_ids,
            event_type=CampaignEventTypeEnum.STOPPED_BUDGET_REACHED,
            event_data={},
        )

    async def create_event_stopped_manually(
        self,
        campaign_id: int,
        initiator_id: int,
        metadata: str,
        timestamp: Optional[datetime] = None,
    ) -> int:

        timestamp = timestamp or datetime.now()

        return await self.create_event(
            timestamp=timestamp,
            campaign_id=campaign_id,
            event_type=CampaignEventTypeEnum.STOPPED_MANUALLY,
            event_data={"initiator": initiator_id, "metadata": metadata},
        )

    async def create_event_end_datetime_changed(
        self,
        campaign_id: int,
        initiator_id: int,
        prev_datetime: datetime,
        timestamp: Optional[datetime] = None,
    ) -> int:

        timestamp = timestamp or datetime.now()

        return await self.create_event(
            timestamp=timestamp,
            campaign_id=campaign_id,
            event_type=CampaignEventTypeEnum.END_DATE_CHANGED,
            event_data={"initiator": initiator_id, "prev_datetime": str(prev_datetime)},
        )

    async def create_event_budget_decreased(
        self,
        campaign_id: int,
        initiator_id: int,
        prev_budget: Decimal,
        timestamp: Optional[datetime] = None,
    ) -> int:

        timestamp = timestamp or datetime.now()

        return await self.create_event(
            timestamp=timestamp,
            campaign_id=campaign_id,
            event_type=CampaignEventTypeEnum.BUDGET_DECREASED,
            event_data={"initiator": initiator_id, "prev_budget": str(prev_budget)},
        )
