from __future__ import annotations

from datetime import datetime
import re
from typing import Any, Dict, Optional

from analogai.foundation.modules.fact.basefact import BaseFact
from analogai.foundation.modules.fact.fact_builder import build_fact
from analogai.foundation.modules.fact.notion_fact import NotionFact
from analogai.foundation.modules.fact.direct_statement_fact import DirectStatementFact
from analogai.foundation.modules.fact.hypothetical_statement_fact import (
    HypotheticalStatementFact,
)
from analogai.foundation.modules.fact.enums.fact_reference_type import FactReferenceType
from analogai.foundation.modules.fact.source.fact_source import (
    FactSource,
    FileSource,
    OtherSource,
    SourceType,
    UrlSource,
)


class FactStorageMapper:
    @staticmethod
    def to_document(fact: BaseFact) -> Dict[str, Any]:
        reference_type = None
        if isinstance(fact, HypotheticalStatementFact):
            reference_type = FactReferenceType.HYPOTHETICAL_STATEMENT.value
        elif isinstance(fact, DirectStatementFact):
            reference_type = FactReferenceType.STATEMENT.value
        elif isinstance(fact, NotionFact):
            reference_type = FactReferenceType.NOTION.value
        return {
            "_id": fact.id,
            "turn_id": fact.turn_id,
            "statement_id": getattr(fact, "statement_id", None),
            "notion_id": getattr(fact, "notion_id", None),
            "reference_type": reference_type,
            "user_id": fact.user_id,
            "agent_id": fact.agent_id,
            "text": getattr(fact, "text", ""),
            "embedding": fact.embedding,
            "source_id": fact.source_id,
            "prev_id": fact.prev_id,
            "next_id": fact.next_id,
            "created_at": _serialize_datetime(fact.created_at),
        }

    @staticmethod
    def from_document(doc: Optional[Dict[str, Any]]) -> Optional[BaseFact]:
        if not doc:
            return None
        embedding = doc.get("embedding")
        if embedding is not None and not isinstance(embedding, list):
            embedding = None
        source_id = doc.get("source_id")
        if not source_id:
            source_type = doc.get("source_type")
            source_value = doc.get("source_value")
            if source_type and source_value is not None:
                try:
                    value = str(source_value) if source_value is not None else None
                    source = _make_source(
                        _parse_source_type(source_type),
                        value,
                        str(doc.get("agent_id")) if doc.get("agent_id") else None,
                    )
                except Exception:
                    value = str(source_value) if source_value is not None else None
                    source = OtherSource(value, agent_id=str(doc.get("agent_id")) if doc.get("agent_id") else None)
                source_id = source.id if source is not None else None
        return build_fact(
            id=str(doc.get("_id")),
            turn_id=doc.get("turn_id"),
            statement_id=doc.get("statement_id"),
            notion_id=doc.get("notion_id"),
            reference_type=doc.get("reference_type"),
            user_id=str(doc.get("user_id")),
            agent_id=str(doc.get("agent_id")),
            text=str(doc.get("text") or ""),
            embedding=embedding,
            source_id=str(source_id) if source_id else None,
            prev_id=doc.get("prev_id"),
            next_id=doc.get("next_id"),
            created_at=_parse_datetime(doc.get("created_at")),
        )


def _parse_source_type(value: object) -> SourceType:
    if value is None:
        return SourceType.OTHER
    raw = str(value).strip()
    if not raw:
        return SourceType.OTHER
    try:
        return SourceType(raw)
    except Exception:
        try:
            return SourceType[raw.upper()]
        except Exception:
            return SourceType.OTHER


def _make_source(source_type: SourceType, value: Optional[str], agent_id: Optional[str]) -> FactSource:
    if source_type == SourceType.FILE:
        return FileSource(value, agent_id=agent_id)
    if source_type in {SourceType.WEBSITE, SourceType.URL}:
        return UrlSource(value, agent_id=agent_id)
    if source_type == SourceType.OTHER:
        return OtherSource(value, agent_id=agent_id)
    return FactSource(source_type, value, agent_id=agent_id)


def _serialize_datetime(value: Optional[datetime]) -> Optional[str]:
    if value is None:
        return None
    if isinstance(value, datetime):
        return value.isoformat()
    return str(value) if value else None


_ISO_PATTERN = re.compile(
    r"^\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})?$"
)


def _parse_datetime(value: object | None) -> Optional[datetime]:
    if value is None:
        return None
    if isinstance(value, datetime):
        return value
    text = str(value).strip()
    if not text:
        return None
    if not _ISO_PATTERN.match(text):
        return None
    if text.endswith("Z"):
        text = f"{text[:-1]}+00:00"
    return datetime.fromisoformat(text)
