from analogai.foundation.common.dtos.notion_expression import NotionExpression
from analogai.foundation.settings import config
from analogai.foundation.modules.notion.notion_repository import NotionRepository
from analogai.foundation.common.utils.attribute_key_resolvers import AttributeKeyResolverChain
from analogai.foundation.modules.notion.notion import Notion
from analogai.foundation.core.observables.notion_observable_service import NotionObservableService
from typeguard import typechecked
from typing import Optional, List


def _environment() -> str:
    value = config.get("ENVIRONMENT")
    return str(value).strip().lower()


def _use_vector_search() -> bool:
    if _environment() in {"test", "testing"}:
        return False
    return bool(config.get("vector.enabled", True))


def _similarity_cutoff() -> float:
    value = config.get("vector.cutoff", 0.4)
    try:
        return float(value)
    except (TypeError, ValueError):
        return 0.4


class NotionService(NotionObservableService):
    def __init__(self, notion_repository: NotionRepository):
        NotionObservableService.__init__(self)
        self.notion_repository = notion_repository

    @typechecked
    def create(self, user_id: str, agent_id: str, expression: NotionExpression) -> Notion:
        notion = Notion(
            user_id=user_id,
            agent_id=agent_id,
            caption=expression.caption,
            attributes=expression.attributes,
        )
        created_notion = self.notion_repository.create(notion)
        self.notify_notion_created(created_notion)
        return created_notion
    
    @typechecked
    def find_or_create(
        self,
        user_id: str,
        agent_id: str,
        expression: NotionExpression,
        similarity_threshold: Optional[float] = None,
    ) -> Notion:
        if similarity_threshold is None:
            existing = self.notion_repository.find(
                agent_id,
                expression,
                None,
                similarity_threshold=1.0,
            )
            if (
                existing
                and existing.caption.strip().lower() == expression.caption.strip().lower()
            ):
                return existing
        else:
            existing = self.notion_repository.find(
                agent_id,
                expression,
                None,
                similarity_threshold=similarity_threshold,
            )
            if existing:
                return existing

        return self.create(user_id, agent_id, expression)

    @typechecked
    def upsert_from_expression(
        self,
        user_id: str,
        agent_id: str,
        expression: NotionExpression,
        similarity_threshold: Optional[float] = None,
    ) -> Notion:
        existing = self.find(agent_id, expression, similarity_threshold=similarity_threshold)
        merged_attributes = self._merge_attributes(
            existing.attributes if existing else None,
            expression.attributes,
        )
        notion = Notion(
            user_id=user_id,
            agent_id=agent_id,
            caption=expression.caption,
            id=existing.id if existing else None,
            attributes=merged_attributes,
        )
        created = self.notion_repository.create(notion)
        if existing is None:
            self.notify_notion_created(created)
        return created
        
    @typechecked
    def find(
        self,
        agent_id: str,
        notion_expression: NotionExpression,
        similarity_threshold: Optional[float] = None,
    ) -> Optional[Notion]:
        if similarity_threshold is None and _use_vector_search():
            similarity_threshold = _similarity_cutoff()
        return self.notion_repository.find(
            agent_id,
            notion_expression,
            None,
            similarity_threshold=similarity_threshold,
        )

    @typechecked
    def get_all_for_agent(self, agent_id: str) -> List[Notion]:
        return self.notion_repository.find_all_by_agent(agent_id)

    @staticmethod
    def _merge_attributes(
        existing: Optional[List[object]],
        incoming: Optional[List[object]],
    ) -> Optional[List[object]]:
        if not existing and not incoming:
            return None
        merged: List[object] = []
        seen = set()
        key_resolver = AttributeKeyResolverChain()
        for attribute_group in (existing or [], incoming or []):
            for attribute in attribute_group:
                attribute_key = key_resolver.resolve(attribute)
                if attribute_key in seen:
                    continue
                seen.add(attribute_key)
                merged.append(attribute)
        return merged