"""
임베딩 생성 유틸리티
"""
import openai
from typing import List, Union
from tenacity import retry, stop_after_attempt, wait_exponential
import logging

logger = logging.getLogger(__name__)


class EmbeddingGenerator:
    """OpenAI Embedding 생성기"""

    def __init__(
        self,
        api_key: str,
        endpoint: str = None,
        model: str = "text-embedding-ada-002",
        dimension: int = 1536,
        api_version: str = None
    ):
        """
        초기화

        Args:
            api_key: OpenAI API 키
            endpoint: 엔드포인트 (선택사항, Azure 전용)
            model: 임베딩 모델명
            dimension: 임베딩 차원
            api_version: API 버전 (선택사항, Azure 전용)
        """
        # Azure OpenAI 또는 일반 OpenAI 자동 선택
        if endpoint and "azure" in endpoint.lower():
            # Azure OpenAI 사용
            self.client = openai.AzureOpenAI(
                api_key=api_key,
                azure_endpoint=endpoint,
                api_version=api_version
            )
        else:
            # 일반 OpenAI 사용
            self.client = openai.OpenAI(
                api_key=api_key
            )
        self.model = model
        self.dimension = dimension

    @retry(
        stop=stop_after_attempt(3),
        wait=wait_exponential(multiplier=1, min=2, max=10)
    )
    def generate_embedding(self, text: str) -> List[float]:
        """
        단일 텍스트의 임베딩 생성

        Args:
            text: 입력 텍스트

        Returns:
            임베딩 벡터 (1536차원)
        """
        try:
            response = self.client.embeddings.create(
                model=self.model,
                input=text
            )
            embedding = response.data[0].embedding

            # 차원 검증
            if len(embedding) != self.dimension:
                raise ValueError(
                    f"임베딩 차원 불일치: 예상 {self.dimension}, 실제 {len(embedding)}"
                )

            return embedding

        except Exception as e:
            logger.error(f"임베딩 생성 실패: {str(e)}")
            raise

    @retry(
        stop=stop_after_attempt(3),
        wait=wait_exponential(multiplier=1, min=2, max=10)
    )
    def generate_embeddings_batch(
        self,
        texts: List[str],
        batch_size: int = 50
    ) -> List[List[float]]:
        """
        배치 텍스트의 임베딩 생성

        Args:
            texts: 입력 텍스트 리스트
            batch_size: 배치 크기 (최대 50)

        Returns:
            임베딩 벡터 리스트
        """
        if not texts:
            return []

        all_embeddings = []

        # 배치 단위로 처리
        for i in range(0, len(texts), batch_size):
            batch = texts[i:i + batch_size]

            try:
                response = self.client.embeddings.create(
                    model=self.model,
                    input=batch
                )

                batch_embeddings = [item.embedding for item in response.data]

                # 차원 검증
                for embedding in batch_embeddings:
                    if len(embedding) != self.dimension:
                        raise ValueError(
                            f"임베딩 차원 불일치: 예상 {self.dimension}, 실제 {len(embedding)}"
                        )

                all_embeddings.extend(batch_embeddings)

                logger.info(f"배치 {i//batch_size + 1}: {len(batch)}개 임베딩 생성 완료")

            except Exception as e:
                logger.error(f"배치 임베딩 생성 실패: {str(e)}")
                raise

        return all_embeddings

    def get_token_count(self, text: str) -> int:
        """
        텍스트의 토큰 수 계산 (근사치)

        Args:
            text: 입력 텍스트

        Returns:
            토큰 수
        """
        # 간단한 추정: 한글은 1글자당 약 1.5 토큰, 영어는 0.75 토큰
        korean_chars = sum(1 for c in text if ord(c) >= 0xAC00 and ord(c) <= 0xD7A3)
        other_chars = len(text) - korean_chars

        estimated_tokens = int(korean_chars * 1.5 + other_chars * 0.75)

        return estimated_tokens


def cosine_similarity(vec1: List[float], vec2: List[float]) -> float:
    """
    코사인 유사도 계산

    Args:
        vec1: 벡터 1
        vec2: 벡터 2

    Returns:
        코사인 유사도 (0.0 ~ 1.0)
    """
    import numpy as np

    vec1_np = np.array(vec1)
    vec2_np = np.array(vec2)

    dot_product = np.dot(vec1_np, vec2_np)
    norm1 = np.linalg.norm(vec1_np)
    norm2 = np.linalg.norm(vec2_np)

    if norm1 == 0 or norm2 == 0:
        return 0.0

    similarity = dot_product / (norm1 * norm2)

    # -1 ~ 1 범위를 0 ~ 1로 변환
    return (similarity + 1) / 2
