"""
PostgreSQL + pgvector 용어집 DB
"""
import psycopg2
from psycopg2.extras import RealDictCursor
from typing import List, Optional, Dict, Any
from contextlib import contextmanager
import logging
import json

from ..models.term import Term
from ..utils.embedding import cosine_similarity

logger = logging.getLogger(__name__)


class PostgresVectorDB:
    """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_term(row: Dict[str, Any]) -> Term:
        """
        데이터베이스 row를 Term 객체로 변환

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

        Returns:
            Term 객체
        """
        term_dict = dict(row)

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

        term_dict.pop('embedding')
        return Term(**term_dict)

    def init_database(self):
        """
        데이터베이스 초기화 (테이블 및 인덱스 생성)
        """
        with self.get_connection() as conn:
            with conn.cursor() as cur:
                # pgvector 확장 설치
                cur.execute("CREATE EXTENSION IF NOT EXISTS vector")

                # terms 테이블 생성
                cur.execute("""
                    CREATE TABLE IF NOT EXISTS terms (
                        term_id VARCHAR(100) PRIMARY KEY,
                        term_name VARCHAR(200) NOT NULL,
                        normalized_name VARCHAR(200) NOT NULL,
                        category VARCHAR(100),
                        definition TEXT NOT NULL,
                        context TEXT,
                        synonyms JSONB DEFAULT '[]',
                        related_terms JSONB DEFAULT '[]',
                        document_source JSONB,
                        confidence_score DECIMAL(3,2) DEFAULT 0.0,
                        usage_count INTEGER DEFAULT 0,
                        last_updated VARCHAR(50),
                        embedding vector(1536),
                        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                        updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
                    )
                """)

                # 인덱스 생성
                cur.execute("""
                    CREATE INDEX IF NOT EXISTS idx_terms_normalized_name
                    ON terms(normalized_name)
                """)

                cur.execute("""
                    CREATE INDEX IF NOT EXISTS idx_terms_category
                    ON terms(category)
                """)

                cur.execute("""
                    CREATE INDEX IF NOT EXISTS idx_terms_confidence
                    ON terms(confidence_score DESC)
                """)

                # 벡터 유사도 검색용 인덱스 (IVFFlat)
                cur.execute("""
                    CREATE INDEX IF NOT EXISTS idx_terms_embedding
                    ON terms USING ivfflat (embedding vector_cosine_ops)
                    WITH (lists = 100)
                """)

                # term_usage_logs 테이블 (사용 이력)
                cur.execute("""
                    CREATE TABLE IF NOT EXISTS term_usage_logs (
                        log_id SERIAL PRIMARY KEY,
                        term_id VARCHAR(100) REFERENCES terms(term_id) ON DELETE CASCADE,
                        user_id VARCHAR(100),
                        meeting_id VARCHAR(100),
                        action VARCHAR(20),
                        feedback_rating INTEGER,
                        feedback_comment TEXT,
                        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
                    )
                """)

                cur.execute("""
                    CREATE INDEX IF NOT EXISTS idx_usage_term_id
                    ON term_usage_logs(term_id, created_at DESC)
                """)

                conn.commit()
                logger.info("PostgreSQL 데이터베이스 초기화 완료")

    def insert_term(self, term: Term) -> bool:
        """
        용어 삽입

        Args:
            term: 용어 객체

        Returns:
            성공 여부
        """
        try:
            with self.get_connection() as conn:
                with conn.cursor() as cur:
                    cur.execute("""
                        INSERT INTO terms (
                            term_id, term_name, normalized_name, category,
                            definition, context, synonyms, related_terms,
                            document_source, confidence_score, usage_count,
                            last_updated, embedding
                        ) VALUES (
                            %s, %s, %s, %s, %s, %s, %s::jsonb, %s::jsonb,
                            %s::jsonb, %s, %s, %s, %s::vector
                        )
                        ON CONFLICT (term_id) DO UPDATE SET
                            term_name = EXCLUDED.term_name,
                            normalized_name = EXCLUDED.normalized_name,
                            category = EXCLUDED.category,
                            definition = EXCLUDED.definition,
                            context = EXCLUDED.context,
                            synonyms = EXCLUDED.synonyms,
                            related_terms = EXCLUDED.related_terms,
                            document_source = EXCLUDED.document_source,
                            confidence_score = EXCLUDED.confidence_score,
                            usage_count = EXCLUDED.usage_count,
                            last_updated = EXCLUDED.last_updated,
                            embedding = EXCLUDED.embedding,
                            updated_at = CURRENT_TIMESTAMP
                    """, (
                        term.term_id,
                        term.term_name,
                        term.normalized_name,
                        term.category,
                        term.definition,
                        term.context,
                        psycopg2.extras.Json(term.synonyms),
                        psycopg2.extras.Json(term.related_terms),
                        psycopg2.extras.Json(term.document_source.dict() if term.document_source else None),
                        term.confidence_score,
                        term.usage_count,
                        term.last_updated,
                        term.embedding
                    ))

                conn.commit()
                return True

        except Exception as e:
            logger.error(f"용어 삽입 실패 ({term.term_id}): {str(e)}")
            return False

    def get_term_by_id(self, term_id: str) -> Optional[Term]:
        """
        ID로 용어 조회

        Args:
            term_id: 용어 ID

        Returns:
            용어 객체 또는 None
        """
        with self.get_connection() as conn:
            with conn.cursor(cursor_factory=RealDictCursor) as cur:
                cur.execute("""
                    SELECT * FROM terms WHERE term_id = %s
                """, (term_id,))

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

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

        Args:
            query: 검색 쿼리
            top_k: 반환할 최대 결과 수
            confidence_threshold: 최소 신뢰도 임계값

        Returns:
            검색 결과 리스트
        """
        normalized_query = query.lower().strip()

        with self.get_connection() as conn:
            with conn.cursor(cursor_factory=RealDictCursor) as cur:
                cur.execute("""
                    SELECT *,
                           CASE
                               WHEN normalized_name = %s THEN 1.0
                               WHEN normalized_name LIKE %s THEN 0.9
                               WHEN term_name ILIKE %s THEN 0.8
                               WHEN synonyms::text ILIKE %s THEN 0.7
                               ELSE 0.5
                           END as match_score
                    FROM terms
                    WHERE (
                        normalized_name LIKE %s
                        OR term_name ILIKE %s
                        OR synonyms::text ILIKE %s
                        OR definition ILIKE %s
                    )
                    AND confidence_score >= %s
                    ORDER BY match_score DESC, confidence_score DESC, usage_count DESC
                    LIMIT %s
                """, (
                    normalized_query,
                    f"%{normalized_query}%",
                    f"%{query}%",
                    f"%{query}%",
                    f"%{normalized_query}%",
                    f"%{query}%",
                    f"%{query}%",
                    f"%{query}%",
                    confidence_threshold,
                    top_k
                ))

                results = []
                for row in cur.fetchall():
                    term_dict = dict(row)
                    match_score = term_dict.pop("match_score")
                    results.append({
                        "term": self._row_to_term(term_dict),
                        "relevance_score": float(match_score),
                        "match_type": "keyword"
                    })

                return results

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

        Args:
            query_embedding: 쿼리 임베딩 벡터
            top_k: 반환할 최대 결과 수
            confidence_threshold: 최소 신뢰도 임계값

        Returns:
            검색 결과 리스트
        """
        with self.get_connection() as conn:
            with conn.cursor(cursor_factory=RealDictCursor) as cur:
                cur.execute("""
                    SELECT *,
                           1 - (embedding <=> %s::vector) as similarity_score
                    FROM terms
                    WHERE confidence_score >= %s
                      AND embedding IS NOT NULL
                    ORDER BY embedding <=> %s::vector
                    LIMIT %s
                """, (
                    query_embedding,
                    confidence_threshold,
                    query_embedding,
                    top_k
                ))

                results = []
                for row in cur.fetchall():
                    term_dict = dict(row)
                    similarity_score = term_dict.pop("similarity_score")
                    results.append({
                        "term": self._row_to_term(term_dict),
                        "relevance_score": float(similarity_score),
                        "match_type": "vector"
                    })

                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_terms,
                        AVG(confidence_score) as avg_confidence
                    FROM terms
                """)
                overall = cur.fetchone()

                # 카테고리별 통계
                cur.execute("""
                    SELECT category, COUNT(*) as count
                    FROM terms
                    GROUP BY category
                    ORDER BY count DESC
                """)
                by_category = {row["category"]: row["count"] for row in cur.fetchall()}

                return {
                    "total_terms": overall["total_terms"],
                    "avg_confidence": float(overall["avg_confidence"]) if overall["avg_confidence"] else 0.0,
                    "by_category": by_category
                }
