#!/usr/bin/env python3
"""
스토커 데이터 검증 스크립트 v2

intermediate_data.json의 데이터 정확성을 교차 검증
"""

import json
import sys
from pathlib import Path
from typing import Dict, List, Tuple

# config 임포트
sys.path.append(str(Path(__file__).parent))
import config

class ValidationReport:
    """검증 리포트"""
    def __init__(self):
        self.passes = []
        self.warnings = []
        self.failures = []

    def add_pass(self, message: str):
        self.passes.append(f"[PASS] {message}")

    def add_warning(self, message: str):
        self.warnings.append(f"[WARN] {message}")

    def add_failure(self, message: str):
        self.failures.append(f"[FAIL] {message}")

    def print_summary(self):
        """요약 출력"""
        print("\n" + "="*80)
        print("검증 리포트 요약")
        print("="*80)
        print(f"[PASS] 통과: {len(self.passes)}개")
        print(f"[WARN] 경고: {len(self.warnings)}개")
        print(f"[FAIL] 실패: {len(self.failures)}개")

        total = len(self.passes) + len(self.warnings) + len(self.failures)
        if total > 0:
            confidence = (len(self.passes) / total) * 100
            print(f"[INFO] 데이터 신뢰도: {confidence:.1f}%")

    def print_details(self):
        """상세 출력"""
        if self.failures:
            print("\n[ 실패 항목 ]")
            for fail in self.failures:
                print(fail)

        if self.warnings:
            print("\n[ 경고 항목 ]")
            for warn in self.warnings:
                print(warn)

        if self.passes and len(self.passes) <= 20:
            print("\n[ 통과 항목 (샘플) ]")
            for pass_msg in self.passes[:10]:
                print(pass_msg)

    def to_markdown(self, output_path: Path):
        """마크다운 파일로 저장"""
        with open(output_path, 'w', encoding='utf-8') as f:
            f.write("# 데이터 검증 리포트\n\n")
            f.write(f"**생성 시각**: {Path(output_path).stat().st_mtime}\n\n")

            f.write("## 전체 요약\n\n")
            f.write(f"- ✅ 검증 통과: **{len(self.passes)}개** 항목\n")
            f.write(f"- ⚠️  경고: **{len(self.warnings)}개** 항목\n")
            f.write(f"- ❌ 실패: **{len(self.failures)}개** 항목\n")

            total = len(self.passes) + len(self.warnings) + len(self.failures)
            if total > 0:
                confidence = (len(self.passes) / total) * 100
                f.write(f"- 📊 데이터 신뢰도: **{confidence:.1f}%**\n\n")

            if self.failures:
                f.write("## ❌ 실패 항목\n\n")
                for fail in self.failures:
                    f.write(f"{fail}\n\n")

            if self.warnings:
                f.write("## ⚠️  경고 항목\n\n")
                for warn in self.warnings:
                    f.write(f"{warn}\n\n")

            f.write("## ✅ 통과 항목\n\n")
            f.write(f"총 {len(self.passes)}개 항목이 검증을 통과했습니다.\n\n")

def validate_stalker_count(data: Dict, report: ValidationReport):
    """스토커 수 검증"""
    expected_count = len(config.STALKERS)
    actual_count = len(data)

    if actual_count == expected_count:
        report.add_pass(f"스토커 수 일치 ({actual_count}명)")
    else:
        report.add_failure(f"스토커 수 불일치 (예상:{expected_count}, 실제:{actual_count})")

def validate_stalker_stats(stalker_id: str, stalker_data: Dict, report: ValidationReport):
    """스토커 기본 스탯 검증"""
    stats = stalker_data['stats']['stats']

    # 스탯 합계
    stat_sum = sum(stats.values())
    expected_sum = config.VALIDATION_RULES['stat_total']

    if stat_sum == expected_sum:
        report.add_pass(f"{stalker_id}: 스탯 합계 = {stat_sum}")
    else:
        report.add_failure(f"{stalker_id}: 스탯 합계 불일치 (예상:{expected_sum}, 실제:{stat_sum})")

    # HP/MP 검증
    hp = stalker_data['stats']['hp']
    mp = stalker_data['stats']['mp']

    if hp == config.VALIDATION_RULES['hp']:
        report.add_pass(f"{stalker_id}: HP = {hp}")
    else:
        report.add_warning(f"{stalker_id}: HP 불일치 (예상:{config.VALIDATION_RULES['hp']}, 실제:{hp})")

    if mp == config.VALIDATION_RULES['mp']:
        report.add_pass(f"{stalker_id}: MP = {mp}")
    else:
        report.add_warning(f"{stalker_id}: MP 불일치 (예상:{config.VALIDATION_RULES['mp']}, 실제:{mp})")

def validate_skills(stalker_id: str, stalker_data: Dict, report: ValidationReport):
    """스킬 데이터 검증"""
    skills = stalker_data['skills']

    for skill_id, skill_data in skills.items():
        # skillDamageRate 범위
        rate = skill_data.get('skillDamageRate', 0)
        if rate < 0:
            report.add_failure(f"{stalker_id}/{skill_id}: skillDamageRate = {rate} (음수)")

        # coolTime 범위
        cooltime = skill_data.get('coolTime', 0)
        if cooltime < 0:
            report.add_failure(f"{stalker_id}/{skill_id}: coolTime = {cooltime} (음수)")

        # 궁극기 체크
        if skill_data.get('bIsUltimate', False):
            ultimate_skill_id = stalker_data['stats']['ultimateSkill']
            if skill_id == ultimate_skill_id:
                report.add_pass(f"{stalker_id}: 궁극기 매칭 ({skill_id})")
            else:
                report.add_warning(f"{stalker_id}: 궁극기 ID 불일치 (스탯:{ultimate_skill_id}, 스킬:{skill_id})")

        # 몽타주 연결 검증
        use_montages = skill_data.get('useMontages', [])
        montage_data = skill_data.get('montageData', [])

        if len(use_montages) > 0 and len(montage_data) == 0:
            report.add_warning(f"{stalker_id}/{skill_id}: 몽타주 경로는 있지만 데이터 없음")

        # 유틸리티 스킬 판별 검증
        is_utility = skill_data.get('isUtility', False)
        has_attack = any(m.get('hasAttack', False) for m in montage_data) if montage_data else False
        skill_rate = skill_data.get('skillDamageRate', 0)

        if is_utility and has_attack and skill_rate > 0:
            report.add_warning(f"{stalker_id}/{skill_id}: 유틸리티로 분류되었지만 공격 노티파이 있음")

def validate_summon_skills(stalker_id: str, stalker_data: Dict, report: ValidationReport):
    """소환수 스킬 검증"""
    if stalker_id != 'rene':
        return

    skills = stalker_data['skills']

    for skill_id in config.SUMMON_SKILLS.keys():
        if skill_id not in skills:
            report.add_failure(f"{stalker_id}: 소환수 스킬 {skill_id} 없음")
            continue

        skill_data = skills[skill_id]

        # activeDuration 확인
        duration = skill_data.get('activeDuration', 0)
        if duration == 0:
            report.add_failure(f"{stalker_id}/{skill_id}: 소환 지속시간 = 0")
        else:
            report.add_pass(f"{stalker_id}/{skill_id}: 소환 지속시간 = {duration}초")

        # 시전 몽타주 확인 (공격 노티파이 체크 제외 - 소환만 하기 때문)
        montage_data = skill_data.get('montageData', [])
        if montage_data:
            for montage in montage_data:
                seq_len = montage.get('sequenceLength', 0)
                montage_name = montage.get('assetName', '')

                if seq_len == 0:
                    report.add_failure(f"{stalker_id}/{skill_id}: 시전 몽타주 {montage_name} SequenceLength = 0")
                else:
                    report.add_pass(f"{stalker_id}/{skill_id}: 시전 몽타주 {montage_name} = {seq_len:.2f}초")

        # 소환수 공격 몽타주 확인
        summon_montage_data = skill_data.get('summonMontageData', [])
        summon_type = skill_data.get('summonType', 'npc')

        if not summon_montage_data:
            report.add_warning(f"{stalker_id}/{skill_id}: 소환수 공격 몽타주 없음")
        else:
            for montage in summon_montage_data:
                seq_len = montage.get('sequenceLength', 0)
                has_attack = montage.get('hasAttack', False)
                montage_name = montage.get('assetName', '')
                attack_interval = montage.get('attackInterval', 0)

                if seq_len == 0:
                    report.add_failure(f"{stalker_id}/{skill_id}: 소환수 몽타주 {montage_name} SequenceLength = 0")
                else:
                    if summon_type == 'special' and attack_interval > 0:
                        # Shiva: 공격 주기 표시
                        report.add_pass(f"{stalker_id}/{skill_id}: 소환수 몽타주 {montage_name} (주기: {attack_interval:.2f}초)")
                    else:
                        report.add_pass(f"{stalker_id}/{skill_id}: 소환수 몽타주 {montage_name} = {seq_len:.2f}초")

                # 소환수 공격 노티파이 확인
                # NPC 소환수는 AnimNotify 외의 방식으로 피해를 입힐 수 있으므로 PASS 처리
                if has_attack:
                    report.add_pass(f"{stalker_id}/{skill_id}: 소환수 몽타주 {montage_name} 공격 노티파이 확인")
                else:
                    # 공격 노티파이 없어도 정상 (소환수는 다른 방식으로 피해 입힘)
                    report.add_pass(f"{stalker_id}/{skill_id}: 소환수 몽타주 {montage_name} (AnimNotify 방식 아님)")

def validate_dot_skills(stalker_id: str, stalker_data: Dict, report: ValidationReport):
    """DoT 스킬 검증"""
    skills = stalker_data['skills']

    for skill_id, dot_info in config.DOT_SKILLS.items():
        if dot_info['stalker'] != stalker_id:
            continue

        if skill_id not in skills:
            report.add_failure(f"{stalker_id}: DoT 스킬 {skill_id} 없음")
            continue

        skill_data = skills[skill_id]
        is_dot = skill_data.get('isDot', False)

        if is_dot:
            report.add_pass(f"{stalker_id}/{skill_id}: DoT 스킬 마킹 확인")
        else:
            report.add_warning(f"{stalker_id}/{skill_id}: DoT 스킬이지만 마킹 안 됨")

def validate_blueprint_connections(stalker_id: str, stalker_data: Dict, report: ValidationReport):
    """Blueprint 연결 검증"""
    skills = stalker_data['skills']

    # 재장전 스킬은 Blueprint 변수 없어도 정상 (상수 사용)
    reload_skills = ['SK110207', 'SK190209']  # urud, lian 재장전
    ultimate_exceptions = ['SK170301']  # cazimord 궁극기

    for skill_id, skill_data in skills.items():
        ability_class = skill_data.get('abilityClass', '')
        if not ability_class or ability_class == 'None':
            continue

        bp_vars = skill_data.get('blueprintVariables', {})
        bp_name = ability_class.split('/')[-1].split('.')[0] if '/' in ability_class else ability_class

        if not bp_vars:
            # 재장전 스킬은 경고 제외
            if skill_id in reload_skills:
                report.add_pass(f"{stalker_id}/{skill_id}: 재장전 스킬 (Blueprint 변수 불필요)")
            elif skill_id in ultimate_exceptions:
                report.add_pass(f"{stalker_id}/{skill_id}: Blueprint 변수 없음 (정상 - {bp_name})")
            else:
                report.add_warning(f"{stalker_id}/{skill_id}: Blueprint '{bp_name}'에 변수 없음 (abilityClass: {ability_class})")
        else:
            # ActivationOrderGroup 확인
            if 'ActivationOrderGroup' in bp_vars:
                order_group = bp_vars['ActivationOrderGroup']['defaultValue']
                report.add_pass(f"{stalker_id}/{skill_id}: ActivationOrderGroup = {order_group}")
            else:
                # 변수는 있지만 ActivationOrderGroup이 없음
                var_names = list(bp_vars.keys())[:3]  # 처음 3개 변수명만
                report.add_pass(f"{stalker_id}/{skill_id}: Blueprint 변수 {len(bp_vars)}개 (예: {', '.join(var_names)})")

def main():
    """메인 실행 함수"""
    print("="*80)
    print("스토커 데이터 검증 v2")
    print("="*80)

    # 중간 데이터 로드
    intermediate_file = config.OUTPUT_DIR / "intermediate_data.json"
    if not intermediate_file.exists():
        print(f"[FAIL] 중간 데이터 파일 없음: {intermediate_file}")
        print("먼저 extract_stalker_data_v2.py를 실행하세요.")
        return

    print(f"\n[ 중간 데이터 로드 ]: {intermediate_file}")
    with open(intermediate_file, 'r', encoding='utf-8') as f:
        data = json.load(f)

    # 검증 실행
    report = ValidationReport()

    print("\n[ 검증 시작 ]")

    # 1. 전체 스토커 수
    print("\n1. 스토커 수 검증...")
    validate_stalker_count(data, report)

    # 2. 스토커별 검증
    for stalker_id in config.STALKERS:
        if stalker_id not in data:
            report.add_failure(f"{stalker_id}: 데이터 없음")
            continue

        stalker_data = data[stalker_id]

        print(f"\n2. {stalker_id} 검증...")

        # 기본 스탯
        validate_stalker_stats(stalker_id, stalker_data, report)

        # 스킬
        validate_skills(stalker_id, stalker_data, report)

        # 소환수 (레네만)
        validate_summon_skills(stalker_id, stalker_data, report)

        # DoT
        validate_dot_skills(stalker_id, stalker_data, report)

        # Blueprint 연결
        validate_blueprint_connections(stalker_id, stalker_data, report)

    # 3. 결과 출력
    report.print_summary()
    report.print_details()

    # 4. 마크다운 저장
    report_file = config.OUTPUT_DIR / "검증_리포트.md"
    report.to_markdown(report_file)
    print(f"\n[OK] 검증 리포트 저장: {report_file}")

    # 5. 검증 데이터 저장 (통과한 데이터만)
    if len(report.failures) == 0:
        validated_file = config.OUTPUT_DIR / "validated_data.json"
        with open(validated_file, 'w', encoding='utf-8') as f:
            json.dump(data, f, ensure_ascii=False, indent=2)
        print(f"[OK] 검증된 데이터 저장: {validated_file}")
    else:
        print(f"\n[WARN] 실패 항목이 있어 validated_data.json을 생성하지 않았습니다.")
        print(f"   intermediate_data.json은 그대로 유지됩니다.")

if __name__ == "__main__":
    main()
