from itertools import groupby
from operator import itemgetter
from typing import Optional

from asyncpg.exceptions import ForeignKeyViolationError
from sqlalchemy import select

from maps_adv.adv_store.lib.db import db
from maps_adv.adv_store.v2.lib.db import tables
from maps_adv.adv_store.api.schemas.enums import CampaignStatusEnum

from .campaign_change_log import (
    CampaignsChangeLogActionName,
    insert_campaign_change_log,
    refresh_campaigns_change_logs,
)
from .exceptions import CampaignNotFound


def unique_justseen(iterable, key):
    """List unique elements, preserving order. Remember only the element just seen.

    https://docs.python.org/3/library/itertools.html#itertools-recipes"""

    # unique_justseen('AAAABBBCCDAABBB') --> A B C D A B
    # unique_justseen('ABBCcAD', str.lower) --> A B C A D
    return map(next, map(itemgetter(1), groupby(iterable, key)))


async def get_current_campaign_status(campaign_id: int) -> CampaignStatusEnum:
    status = await db.ro.fetch_val(
        select([tables.status_history.c.status])
        .where(tables.status_history.c.campaign_id == campaign_id)
        .order_by(tables.status_history.c.changed_datetime.desc())
        .limit(1)
    )

    if status is None:
        raise CampaignNotFound

    return status


async def get_campaign_status_history(campaign_id):
    statuses = await db.ro.fetch_all(
        select(
            [
                tables.status_history.c.author_id,
                tables.status_history.c.status,
                tables.status_history.c.changed_datetime,
                tables.status_history.c.metadata,
            ]
        )
        .where(tables.status_history.c.campaign_id == campaign_id)
        .order_by(tables.status_history.c.changed_datetime)
    )
    return [dict(i) for i in unique_justseen(statuses, itemgetter("status"))]


async def append_status_history_entry(
    campaign_id: int,
    status: CampaignStatusEnum,
    author_id: int,
    metadata: Optional[dict] = None,
    change_log_action_name: str = CampaignsChangeLogActionName.CAMPAIGN_CHANGE_STATUS,
):
    if metadata is None:
        metadata = {}

    try:
        async with db.rw.transaction():
            change_log_id = await insert_campaign_change_log(
                campaign_id=campaign_id,
                author_id=author_id,
                action_name=change_log_action_name,
            )
            await db.rw.execute(
                tables.status_history.insert().values(
                    campaign_id=campaign_id,
                    status=status,
                    author_id=author_id,
                    metadata=metadata,
                )
            )
            await refresh_campaigns_change_logs(ids=[change_log_id])
    except ForeignKeyViolationError as exc:
        if exc.constraint_name == "fk_status_history_campaign_id_campaign":
            raise CampaignNotFound
        else:
            raise
