import math

from sqlalchemy.orm import Session
from app.models.recruitment import Recruitment
from app.models.candidate import Candidate
from app.services.gemini_service import create_embedding, generate_match_reasons, parse_resume, _call_gemini, _parse_json
from app.services.pinecone_service import search_candidates


def _cosine_similarity(a: list[float], b: list[float]) -> float:
    dot = sum(x * y for x, y in zip(a, b))
    norm_a = math.sqrt(sum(x * x for x in a))
    norm_b = math.sqrt(sum(x * x for x in b))
    return dot / (norm_a * norm_b) if (norm_a * norm_b) > 0 else 0.0


def match_candidates_for_jd(jd_id: int, company_id: int, db: Session, top_k: int = 10) -> list[dict]:
    """
    Flow:
        1. MySQL에서 JD 조회
        2. Gemini로 JD 임베딩 생성
        3. Pinecone 검색 (namespace = company_id)
        4. 후보자 상세 정보를 MySQL에서 보완
        5. Gemini로 매칭 사유 생성 (단일 호출)
        6. 점수 정렬 후 반환
    """
    # 1. JD 조회
    jd = db.query(Recruitment).filter(
        Recruitment.id == jd_id,
        Recruitment.company_id == company_id,
    ).first()
    if not jd:
        raise ValueError("채용공고를 찾을 수 없습니다")

    # 2. JD 임베딩 생성
    jd_text = f"{jd.title}\n{jd.description or ''}\n{jd.requirements or ''}"
    jd_embedding = create_embedding(jd_text)

    # 3. Pinecone 검색
    matches = search_candidates(
        jd_embedding=jd_embedding,
        namespace=str(company_id),
        top_k=top_k,
    )
    if not matches:
        return []

    # 4. 후보자 상세 정보 조회 (단일 쿼리)
    candidate_ids = [int(m.id.replace("cand_", "")) for m in matches]
    candidates_db = {
        c.id: c for c in db.query(Candidate).filter(Candidate.id.in_(candidate_ids)).all()
    }

    # 결과 구성
    results = []
    candidates_for_reason = []

    for match in matches:
        cand_id = int(match.id.replace("cand_", ""))
        cand = candidates_db.get(cand_id)
        if not cand:
            continue

        parsed = cand.parsed_data or {}
        results.append({
            "candidate_id": cand.id,
            "name": cand.name,
            "email": cand.email,
            "score": round(match.score * 100, 1),  # 코사인 유사도 → 퍼센트
            "reason": "",
        })
        candidates_for_reason.append({
            "id": str(cand.id),
            "name": cand.name,
            "skills": parsed.get("skills", []),
            "experience_years": parsed.get("experience_years", 0),
        })

    # 5. 매칭 사유 생성 (한 번의 Gemini 호출)
    reasons = generate_match_reasons(
        jd_title=jd.title,
        jd_requirements=jd.requirements or "",
        candidates=candidates_for_reason,
    )
    for r in results:
        r["reason"] = reasons.get(str(r["candidate_id"]), "벡터 유사도 기반 매칭")

    # 6. 점수 정렬
    results.sort(key=lambda x: x["score"], reverse=True)
    return results


# ─────────────────────────────────────
# 미리보기 매칭 (저장 없이 단일 후보자 ↔ JD)
# ─────────────────────────────────────
def preview_match(jd_id: int, resume_text: str, company_id: int, db: Session) -> dict:
    """
    Flow:
        1. MySQL에서 JD 조회
        2. JD 텍스트 → 임베딩
        3. 이력서 텍스트 파싱 (Gemini)
        4. 후보자 텍스트 → 임베딩
        5. 코사인 유사도 → 점수
        6. 매칭 분석 생성 (Gemini)
    """
    # 1. JD 조회
    jd = db.query(Recruitment).filter(
        Recruitment.id == jd_id,
        Recruitment.company_id == company_id,
    ).first()
    if not jd:
        raise ValueError("채용공고를 찾을 수 없습니다")

    jd_text = f"{jd.title}\n{jd.description or ''}\n{jd.requirements or ''}"

    # 2. JD 임베딩
    jd_embedding = create_embedding(jd_text)

    # 3. 이력서 파싱
    parsed = parse_resume(resume_text)

    # 4. 후보자 임베딩
    candidate_text = (
        f"{parsed.get('name', '')}\n"
        f"{', '.join(parsed.get('skills', []))}\n"
        f"경험: {parsed.get('experience_years', 0)}년"
    )
    candidate_embedding = create_embedding(candidate_text)

    # 5. 코사인 유사도 → 퍼센트
    similarity = _cosine_similarity(jd_embedding, candidate_embedding)
    score = round(min(similarity * 100, 100), 1)

    # 6. 매칭 분석 생성
    analysis = _generate_preview_analysis(jd, parsed)

    return {
        "score": score,
        "parsed_data": parsed,
        "analysis": analysis,
    }


def _generate_preview_analysis(jd, parsed: dict) -> list[dict]:
    """Gemini로 매칭 분석 항목 생성, 실패 시 규칙 기반 폴백"""
    prompt = (
        "다음 채용공고와 후보자 정보를 비교하여 적합성 분석을 JSON 배열로 반환하세요.\n"
        "반드시 아래 형식의 JSON 배열만 답변하세요. 다른 텍스트는 포함하지 마세요.\n\n"
        '[{"type":"success 또는 warning","title":"분석 항목 제목","detail":"구체적 설명 1문장"}, ...]\n\n'
        "최대 4개의 항목. success는 적합한 부분, warning은 확인 필요한 부분.\n\n"
        f"채용공고:\n직책: {jd.title}\n요구사항: {jd.requirements or ''}\n\n"
        f"후보자:\n이름: {parsed.get('name', '미확인')}\n"
        f"기술: {', '.join(parsed.get('skills', [])) or '정보 없음'}\n"
        f"경험: {parsed.get('experience_years', 0)}년\n"
        f"학력: {parsed.get('education', '정보 없음')}"
    )
    try:
        raw = _call_gemini(prompt)
        items = _parse_json(raw)
        analysis = []
        for item in (items if isinstance(items, list) else []):
            if isinstance(item, dict) and "type" in item and "title" in item and "detail" in item:
                analysis.append({
                    "type": item["type"] if item["type"] in ("success", "warning") else "warning",
                    "title": str(item["title"]),
                    "detail": str(item["detail"]),
                })
        return analysis[:4]
    except Exception:
        return _rule_based_analysis(jd.requirements or "", parsed)


def _rule_based_analysis(jd_requirements: str, parsed: dict) -> list[dict]:
    """Gemini 실패 시 규칙 기반 폴백"""
    analysis = []
    skills = parsed.get("skills", [])
    exp = parsed.get("experience_years", 0)

    if skills:
        analysis.append({"type": "success", "title": "기술 스택 확인", "detail": f"후보자 기술: {', '.join(skills)}"})
    if exp > 0:
        analysis.append({
            "type": "success" if exp >= 3 else "warning",
            "title": "경험 연수",
            "detail": f"{exp}년 경험 확인됨",
        })
    if not analysis:
        analysis.append({"type": "warning", "title": "정보 부족", "detail": "이력서에서 충분한 정보를 추출할 수 없었습니다."})
    return analysis
