#  Copyright (C) 2023
#  ABM, Moscow
#
#  UNPUBLISHED PROPRIETARY MATERIAL.
#  ALL RIGHTS RESERVED.
#
#  Authors: Vasya Svintsov <v.svintsov@techokert.ru>

import logging
from dataclasses import dataclass, asdict
from enum import StrEnum, unique
from typing import Type, Iterable, Any

from http_tools import AbmServiceConnector
from http_tools.long_requests_middleware import first
from container_tools import conditional_setitem
from custom_json import Jsonable
from type_cast import to_dataclass

from .entity_order import entity_order_as_dict, EntityOrder
from .entity_condition import EntityCondition, entity_condition_as_dict
from .entity_dataclass import Entity, prepare_entity_column_names, is_entity_dataclass
from .to_list import to_list


logger = logging.getLogger(__name__)


class EntityServerConnector:
    @unique
    class Method(StrEnum):
        ADD = 'add'
        GET = 'get'
        DELETE = 'delete'

    Config = AbmServiceConnector.Config

    @dataclass(kw_only=True)
    class Context(AbmServiceConnector.Context):
        project_name: str

    def __init__(self, config: Config, context: Context) -> None:
        self.config = config
        self.context = context
        self._connector = AbmServiceConnector(config, context)

    async def get_one(self,
                      entity_type: Type[Entity],
                      filter_by: EntityCondition | list[EntityCondition] | None = None,
                      search_by: EntityCondition | list[EntityCondition] | None = None,
                      order_by: EntityOrder | list[EntityOrder] | None = None) -> Entity:
        entities = await self.get_list(entity_type, filter_by, search_by, order_by, limit=1)
        if not entities:
            raise LookupError(f'Undefined {entity_type.__entity_name__}')
        return first(entities)

    async def get_list(self,
                       entity_type: Type[Entity],
                       filter_by: EntityCondition | list[EntityCondition] | None = None,
                       search_by: EntityCondition | list[EntityCondition] | None = None,
                       order_by: EntityOrder | list[EntityOrder] | None = None,
                       offset: int = 0,
                       limit: int = 10 ** 6) -> list[Entity]:
        if not is_entity_dataclass(entity_type):
            raise TypeError("expected entity_dataclass type")

        payload = dict(columns=prepare_entity_column_names(entity_type), offset=offset, limit=limit)
        conditional_setitem(
            payload, filter_by=[entity_condition_as_dict(f) for f in to_list(filter_by)] if filter_by else None
        )
        conditional_setitem(
            payload, search_by=[entity_condition_as_dict(s) for s in to_list(search_by)] if search_by else None
        )
        conditional_setitem(
            payload, order_by=[entity_order_as_dict(o) for o in to_list(order_by)] if order_by else None
        )
        entities = await self._execute_post_request(self.Method.GET, entity_type, payload)
        return [to_dataclass(entity_values, entity_type) for entity_values in entities]

    async def add_one(self, entity: Entity) -> int:
        entity_ids = await self.add_list(entity)
        return first(entity_ids)

    async def add_list(self, entity: Entity | Iterable[Entity], *entities: Entity) -> list[int]:
        entities = to_list(entity) + list(entities)
        if not entities:
            raise ValueError(f'empty entities list')

        entity_type = None
        values_to_add = []
        for entity in entities:
            if not is_entity_dataclass(entity):
                raise TypeError("expected entity_dataclass instance")
            if entity_type is not None and not isinstance(entity, entity_type):
                raise TypeError('entities have different types')
            entity_type = type(entity)
            values_to_add.append(asdict(entity))

        payload = dict(values=values_to_add)
        entity_ids = await self._execute_post_request(self.Method.ADD, entity_type, payload)
        return entity_ids

    async def delete(self,
                     entity_type: Type[Entity],
                     filter_by: EntityCondition | list[EntityCondition]) -> bool:
        if not is_entity_dataclass(entity_type):
            raise TypeError("expected entity_dataclass type")

        payload = dict(filter_by=[entity_condition_as_dict(f) for f in to_list(filter_by)])
        deletion_status = await self._execute_post_request(self.Method.DELETE, entity_type, payload)
        return deletion_status

    async def _execute_post_request(self, method: Method, entity_type: Type[Entity], payload: Jsonable) -> Any:
        return await self._connector.post(
            f'/entity/{entity_type.__entity_name__}/{method}', payload=payload, headers=self._construct_headers()
        )

    def _construct_headers(self) -> dict[str, str]:
        return {'server_name': self.context.project_name}
