"""
RAG 회의록 데이터베이스
"""
import psycopg2
from psycopg2.extras import RealDictCursor
from typing import List, Optional, Dict, Any
from contextlib import contextmanager
import logging
import json
from datetime import datetime

from ..models.minutes import RagMinutes, MinutesSection
from ..utils.embedding import cosine_similarity

logger = logging.getLogger(__name__)


class RagMinutesDB:
    """RAG 회의록 PostgreSQL + pgvector 데이터베이스"""

    def __init__(self, connection_string: str):
        """
        초기화

        Args:
            connection_string: PostgreSQL 연결 문자열
        """
        self.connection_string = connection_string

    @contextmanager
    def get_connection(self):
        """데이터베이스 연결 컨텍스트 매니저"""
        conn = psycopg2.connect(self.connection_string)
        try:
            yield conn
        finally:
            conn.close()

    @staticmethod
    def _parse_embedding(embedding_str: Optional[str]) -> Optional[List[float]]:
        """
        PostgreSQL vector 타입을 Python 리스트로 변환

        Args:
            embedding_str: PostgreSQL에서 반환된 vector 문자열 (예: "[-0.003,0.01,...]")

        Returns:
            float 리스트 또는 None
        """
        if not embedding_str:
            return None

        try:
            # vector 타입은 "[1,2,3]" 형태의 문자열로 반환됨
            if isinstance(embedding_str, str):
                return json.loads(embedding_str)
            elif isinstance(embedding_str, list):
                return embedding_str
            return None
        except (json.JSONDecodeError, ValueError) as e:
            logger.error(f"임베딩 파싱 실패: {str(e)}")
            return None

    @staticmethod
    def _row_to_minutes(row: Dict[str, Any]) -> RagMinutes:
        """
        데이터베이스 row를 RagMinutes 객체로 변환

        Args:
            row: 데이터베이스 row (dict)

        Returns:
            RagMinutes 객체
        """
        minutes_dict = dict(row)

        # embedding 필드 파싱
        if "embedding" in minutes_dict:
            minutes_dict["embedding"] = RagMinutesDB._parse_embedding(minutes_dict["embedding"])

        # sections 필드 파싱
        if "sections" in minutes_dict and minutes_dict["sections"]:
            sections_data = minutes_dict["sections"]
            if isinstance(sections_data, str):
                sections_data = json.loads(sections_data)
            minutes_dict["sections"] = [MinutesSection(**section) for section in sections_data]
        else:
            minutes_dict["sections"] = []

        # datetime 필드를 문자열로 변환
        for field in ['scheduled_at', 'finalized_at', 'created_at', 'updated_at']:
            if field in minutes_dict and minutes_dict[field]:
                if isinstance(minutes_dict[field], datetime):
                    minutes_dict[field] = minutes_dict[field].isoformat()
                    
        minutes_dict.pop("embedding")

        return RagMinutes(**minutes_dict)

    def insert_minutes(self, minutes: RagMinutes) -> bool:
        """
        회의록 삽입 또는 업데이트

        Args:
            minutes: 회의록 객체

        Returns:
            성공 여부
        """
        try:
            with self.get_connection() as conn:
                with conn.cursor() as cur:
                    # sections를 JSON으로 변환
                    sections_json = [section.dict() for section in minutes.sections]

                    cur.execute("""
                        INSERT INTO rag_minutes (
                            meeting_id, title, purpose, description, scheduled_at,
                            location, organizer_id, minutes_id, minutes_status,
                            minutes_version, created_by, finalized_by, finalized_at,
                            sections, full_content, embedding
                        ) VALUES (
                            %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s,
                            %s::jsonb, %s, %s::vector
                        )
                        ON CONFLICT (minutes_id) DO UPDATE SET
                            meeting_id = EXCLUDED.meeting_id,
                            title = EXCLUDED.title,
                            purpose = EXCLUDED.purpose,
                            description = EXCLUDED.description,
                            scheduled_at = EXCLUDED.scheduled_at,
                            location = EXCLUDED.location,
                            organizer_id = EXCLUDED.organizer_id,
                            minutes_status = EXCLUDED.minutes_status,
                            minutes_version = EXCLUDED.minutes_version,
                            finalized_by = EXCLUDED.finalized_by,
                            finalized_at = EXCLUDED.finalized_at,
                            sections = EXCLUDED.sections,
                            full_content = EXCLUDED.full_content,
                            embedding = EXCLUDED.embedding,
                            updated_at = CURRENT_TIMESTAMP
                    """, (
                        minutes.meeting_id,
                        minutes.title,
                        minutes.purpose,
                        minutes.description,
                        minutes.scheduled_at,
                        minutes.location,
                        minutes.organizer_id,
                        minutes.minutes_id,
                        minutes.minutes_status,
                        minutes.minutes_version,
                        minutes.created_by,
                        minutes.finalized_by,
                        minutes.finalized_at,
                        psycopg2.extras.Json(sections_json),
                        minutes.full_content,
                        minutes.embedding
                    ))

                conn.commit()
                logger.info(f"회의록 저장 성공: {minutes.minutes_id}")
                return True

        except Exception as e:
            logger.error(f"회의록 저장 실패 ({minutes.minutes_id}): {str(e)}")
            return False

    def get_minutes_by_id(self, minutes_id: str) -> Optional[RagMinutes]:
        """
        ID로 회의록 조회

        Args:
            minutes_id: 회의록 ID

        Returns:
            회의록 객체 또는 None
        """
        with self.get_connection() as conn:
            with conn.cursor(cursor_factory=RealDictCursor) as cur:
                cur.execute("""
                    SELECT * FROM rag_minutes WHERE minutes_id = %s
                """, (minutes_id,))

                row = cur.fetchone()
                if row:
                    return self._row_to_minutes(row)
                return None

    def search_by_vector(
        self,
        query_embedding: List[float],
        top_k: int = 5,
        similarity_threshold: float = 0.7,
        exclude_minutes_id: Optional[str] = None
    ) -> List[Dict[str, Any]]:
        """
        벡터 유사도 검색

        Args:
            query_embedding: 쿼리 임베딩 벡터
            top_k: 반환할 최대 결과 수
            similarity_threshold: 최소 유사도 임계값
            exclude_minutes_id: 제외할 회의록 ID (연관 회의록 검색 시 자기 자신 제외)

        Returns:
            검색 결과 리스트
        """
        with self.get_connection() as conn:
            with conn.cursor(cursor_factory=RealDictCursor) as cur:
                # 제외 조건 추가
                exclude_condition = ""
                params = [query_embedding, query_embedding, similarity_threshold, query_embedding, top_k]

                if exclude_minutes_id:
                    exclude_condition = "AND minutes_id != %s"
                    # 파라미터 순서: 처음 4개는 embedding 검색용, 5번째는 exclude용, 6번째는 limit용
                    params = [query_embedding, query_embedding, similarity_threshold, exclude_minutes_id, query_embedding, top_k]

                query = f"""
                    SELECT *,
                           1 - (embedding <=> %s::vector) as similarity_score
                    FROM rag_minutes
                    WHERE embedding IS NOT NULL
                      AND 1 - (embedding <=> %s::vector) >= %s
                      {exclude_condition}
                    ORDER BY embedding <=> %s::vector
                    LIMIT %s
                """

                cur.execute(query, params)

                results = []
                for row in cur.fetchall():
                    minutes_dict = dict(row)
                    similarity_score = minutes_dict.pop("similarity_score")
                    results.append({
                        "minutes": self._row_to_minutes(minutes_dict),
                        "similarity_score": float(similarity_score)
                    })

                logger.info(f"벡터 검색 완료: {len(results)}개 결과 (exclude: {exclude_minutes_id})")
                return results

    def search_by_keyword(
        self,
        query: str,
        top_k: int = 5
    ) -> List[Dict[str, Any]]:
        """
        키워드 검색

        Args:
            query: 검색 쿼리
            top_k: 반환할 최대 결과 수

        Returns:
            검색 결과 리스트
        """
        with self.get_connection() as conn:
            with conn.cursor(cursor_factory=RealDictCursor) as cur:
                cur.execute("""
                    SELECT *,
                           ts_rank(to_tsvector('simple', full_content), plainto_tsquery('simple', %s)) as rank_score
                    FROM rag_minutes
                    WHERE to_tsvector('simple', full_content) @@ plainto_tsquery('simple', %s)
                       OR title ILIKE %s
                    ORDER BY rank_score DESC, finalized_at DESC
                    LIMIT %s
                """, (
                    query,
                    query,
                    f"%{query}%",
                    top_k
                ))

                results = []
                for row in cur.fetchall():
                    minutes_dict = dict(row)
                    rank_score = minutes_dict.pop("rank_score", 0.0)
                    results.append({
                        "minutes": self._row_to_minutes(minutes_dict),
                        "similarity_score": float(rank_score) if rank_score else 0.0
                    })

                logger.info(f"키워드 검색 완료: {len(results)}개 결과")
                return results

    def get_stats(self) -> Dict[str, Any]:
        """
        통계 조회

        Returns:
            통계 정보
        """
        with self.get_connection() as conn:
            with conn.cursor(cursor_factory=RealDictCursor) as cur:
                # 전체 통계
                cur.execute("""
                    SELECT
                        COUNT(*) as total_minutes,
                        COUNT(DISTINCT meeting_id) as total_meetings,
                        COUNT(DISTINCT created_by) as total_authors
                    FROM rag_minutes
                """)
                overall = cur.fetchone()

                # 최근 회의록
                cur.execute("""
                    SELECT finalized_at
                    FROM rag_minutes
                    WHERE finalized_at IS NOT NULL
                    ORDER BY finalized_at DESC
                    LIMIT 1
                """)
                latest = cur.fetchone()

                return {
                    "total_minutes": overall["total_minutes"],
                    "total_meetings": overall["total_meetings"],
                    "total_authors": overall["total_authors"],
                    "latest_finalized_at": latest["finalized_at"].isoformat() if latest and latest["finalized_at"] else None
                }

    def delete_minutes(self, minutes_id: str) -> bool:
        """
        회의록 삭제

        Args:
            minutes_id: 회의록 ID

        Returns:
            성공 여부
        """
        try:
            with self.get_connection() as conn:
                with conn.cursor() as cur:
                    cur.execute("""
                        DELETE FROM rag_minutes WHERE minutes_id = %s
                    """, (minutes_id,))

                conn.commit()
                logger.info(f"회의록 삭제 성공: {minutes_id}")
                return True

        except Exception as e:
            logger.error(f"회의록 삭제 실패 ({minutes_id}): {str(e)}")
            return False
