#!/usr/bin/env python3
"""
스토커 DPS 시나리오 계산 v2
- 3개 시나리오 계산: 평타, 로테이션 (30초), 버스트 (10초)
- 특수 상황 분석: DoT, 소환체, 패링
"""

import json
import math
from pathlib import Path
from datetime import datetime
from typing import Dict, List, Tuple, Any
from config import (
    get_output_dir, STALKERS, STALKER_INFO,
    BASE_DAMAGE_FORMULA, DOT_SKILLS, DOT_DAMAGE,
    SUMMON_SKILLS, UTILITY_SKILLS, ANALYSIS_BASELINE
)


def load_validated_data(output_dir: Path) -> Dict:
    """validated_data.json 또는 intermediate_data.json 로드"""
    validated_file = output_dir / "validated_data.json"
    intermediate_file = output_dir / "intermediate_data.json"

    if validated_file.exists():
        data_file = validated_file
        print(f"Using validated_data.json")
    elif intermediate_file.exists():
        data_file = intermediate_file
        print(f"Using intermediate_data.json")
    else:
        raise FileNotFoundError(f"No data file found in {output_dir}")

    with open(data_file, 'r', encoding='utf-8') as f:
        return json.load(f)


def calculate_base_damage(stalker_id: str, stats: Dict) -> float:
    """BaseDamage 계산 (Level 20, GearScore 400 기준)"""
    role = STALKER_INFO[stalker_id]['role']

    # 주 스탯 결정 (실제 높은 스탯 또는 역할 기준)
    if stalker_id == 'hilda':
        # Hilda: STR 20 → Physical STR
        damage_type = 'physical_str'
    elif stalker_id == 'urud':
        # Urud: DEX 20 → Physical DEX
        damage_type = 'physical_dex'
    elif stalker_id == 'nave':
        # Nave: INT 25 → Magical
        damage_type = 'magical'
    elif stalker_id == 'baran':
        # Baran: STR 25 → Physical STR
        damage_type = 'physical_str'
    elif stalker_id == 'rio':
        # Rio: DEX 25 → Physical DEX
        damage_type = 'physical_dex'
    elif stalker_id == 'clad':
        # Clad: STR 15 (not WIS!) → Support
        damage_type = 'support'
    elif stalker_id == 'rene':
        # Rene: INT 20 → Magical
        damage_type = 'magical'
    elif stalker_id == 'sinobu':
        # Sinobu: DEX 25 → Physical DEX
        damage_type = 'physical_dex'
    elif stalker_id == 'lian':
        # Lian: DEX 20 → Physical DEX
        damage_type = 'physical_dex'
    elif stalker_id == 'cazimord':
        # Cazimord: DEX 25, STR 15 → Physical DEX
        damage_type = 'physical_dex'
    else:
        # Default fallback
        damage_type = 'physical_str'

    return BASE_DAMAGE_FORMULA[damage_type](stats)


def calculate_basic_attack_dps(stalker_id: str, stalker_data: Dict, base_damage: float) -> Dict:
    """시나리오 1: 평타 DPS 계산"""
    basic_attacks = stalker_data.get('basicAttacks', {})

    # 첫 번째 무기 타입의 평타 사용 (대부분 한 가지 무기만 사용)
    weapon_type = list(basic_attacks.keys())[0] if basic_attacks else None
    if not weapon_type:
        return {
            'dps': 0,
            'combo_time': 0,
            'total_multiplier': 0,
            'attacks': [],
            'notes': '평타 데이터 없음'
        }

    attacks = basic_attacks[weapon_type]

    # 총 콤보 시간 및 평타 배율 합계 계산
    combo_time = sum(atk['actualDuration'] for atk in attacks)

    # attackMultiplier는 AddNormalAttackPer 값 (음수는 감소, 양수는 증가)
    # 실제 배율 = 1.0 + (attackMultiplier / 100)
    total_multiplier = sum(1.0 + (atk['attackMultiplier'] / 100.0) for atk in attacks)

    # 평타 DPS 계산
    if combo_time > 0:
        basic_dps = (base_damage * total_multiplier) / combo_time
    else:
        basic_dps = 0

    return {
        'dps': round(basic_dps, 2),
        'combo_time': round(combo_time, 2),
        'total_multiplier': round(total_multiplier, 2),
        'base_damage': round(base_damage, 2),
        'attacks': [
            {
                'index': atk['index'],
                'name': atk['montageName'],
                'duration': round(atk['actualDuration'], 2),
                'multiplier': round(1.0 + (atk['attackMultiplier'] / 100.0), 2)
            }
            for atk in attacks
        ],
        'notes': ''
    }


def calculate_skill_rotation_dps(stalker_id: str, stalker_data: Dict, base_damage: float, duration: float = 30.0) -> Dict:
    """시나리오 2: 스킬 로테이션 DPS (30초 기본)"""
    skills = stalker_data.get('skills', {})
    stats = stalker_data['stats']

    # 마나 회복 (0.2/초 + 룬 +70% = 0.34/초)
    mana_regen_rate = stats.get('manaRegen', 0.2) * (1.0 + ANALYSIS_BASELINE['rune_effect']['cooltime_reduction'])

    # 쿨타임 감소 (왜곡 룬 -25%)
    cooltime_reduction = ANALYSIS_BASELINE['rune_effect']['cooltime_reduction']

    # 공격 스킬만 필터링 (유틸리티 제외, 궁극기 제외)
    attack_skills = []
    for skill_id, skill in skills.items():
        if skill_id in UTILITY_SKILLS:
            continue
        if skill.get('bIsUltimate', False):
            continue
        if not skill.get('montageData'):
            continue

        # 시퀀스 길이 계산 (Ready, Equipment 제외)
        sequence_length = sum(
            m['actualDuration']
            for m in skill['montageData']
            if 'Ready' not in m['assetName'] and 'Equipment' not in m['assetName']
        )

        if sequence_length > 0:
            attack_skills.append({
                'id': skill_id,
                'name': skill['name'],
                'damage_rate': skill['skillDamageRate'],
                'cooltime': skill['coolTime'] * (1.0 - cooltime_reduction),
                'casting_time': skill.get('castingTime', 0),
                'sequence_length': sequence_length,
                'mana_cost': skill['manaCost'],
                'skill_type': skill.get('skillAttackType', 'Physical')
            })

    # 쿨타임 짧은 순서로 정렬
    attack_skills.sort(key=lambda x: x['cooltime'])

    # 로테이션 시뮬레이션
    current_time = 0.0
    current_mana = stats.get('mp', 50)
    skill_usage = {s['id']: {'count': 0, 'damage': 0, 'next_available': 0} for s in attack_skills}
    basic_attack_time = 0.0

    # 평타 DPS (필러로 사용)
    basic_result = calculate_basic_attack_dps(stalker_id, stalker_data, base_damage)
    basic_dps = basic_result['dps']

    # 30초 동안 스킬 사용
    time_step = 0.1  # 0.1초 단위로 시뮬레이션

    while current_time < duration:
        # 현재 사용 가능한 스킬 찾기
        skill_used = False

        for skill in attack_skills:
            # 쿨타임 확인
            if skill_usage[skill['id']]['next_available'] > current_time:
                continue

            # 마나 확인
            if current_mana < skill['mana_cost']:
                continue

            # 스킬 사용
            skill_time = skill['casting_time'] + skill['sequence_length']
            if current_time + skill_time > duration:
                break  # 시간 초과

            # 피해 계산
            if 'Magical' in skill['skill_type']:
                skill_damage = base_damage * skill['damage_rate']
            else:
                skill_damage = base_damage * skill['damage_rate']

            skill_usage[skill['id']]['count'] += 1
            skill_usage[skill['id']]['damage'] += skill_damage
            skill_usage[skill['id']]['next_available'] = current_time + skill_time + skill['cooltime']

            current_time += skill_time
            current_mana -= skill['mana_cost']
            skill_used = True
            break

        if not skill_used:
            # 스킬 사용 불가 시 평타 사용
            basic_attack_time += time_step
            current_time += time_step

        # 마나 회복
        current_mana = min(current_mana + mana_regen_rate * time_step, stats.get('mp', 50))

    # 총 피해 계산
    total_skill_damage = sum(usage['damage'] for usage in skill_usage.values())
    basic_damage = basic_dps * basic_attack_time
    total_damage = total_skill_damage + basic_damage

    rotation_dps = total_damage / duration

    return {
        'dps': round(rotation_dps, 2),
        'duration': duration,
        'base_damage': round(base_damage, 2),
        'skill_damage': round(total_skill_damage, 2),
        'basic_damage': round(basic_damage, 2),
        'basic_attack_time': round(basic_attack_time, 2),
        'skill_usage': {
            skill_id: {
                'name': next((s['name'] for s in attack_skills if s['id'] == skill_id), ''),
                'count': usage['count'],
                'damage': round(usage['damage'], 2)
            }
            for skill_id, usage in skill_usage.items() if usage['count'] > 0
        },
        'notes': ''
    }


def calculate_burst_dps(stalker_id: str, stalker_data: Dict, base_damage: float, duration: float = 10.0) -> Dict:
    """시나리오 3: 버스트 DPS (10초)"""
    skills = stalker_data.get('skills', {})
    stats = stalker_data['stats']

    # 궁극기 찾기 (유틸리티 제외)
    ultimate_skill = None
    ultimate_id = None
    for skill_id, skill in skills.items():
        if skill.get('bIsUltimate', False) and skill_id not in UTILITY_SKILLS:
            ultimate_skill = skill
            ultimate_id = skill_id
            break

    # 모든 공격 스킬 (유틸리티 제외)
    attack_skills = []
    for skill_id, skill in skills.items():
        if skill_id in UTILITY_SKILLS:
            continue
        if skill.get('bIsUltimate', False):
            continue
        if not skill.get('montageData'):
            continue

        # 시퀀스 길이 계산
        sequence_length = sum(
            m['actualDuration']
            for m in skill['montageData']
            if 'Ready' not in m['assetName'] and 'Equipment' not in m['assetName']
        )

        if sequence_length > 0:
            attack_skills.append({
                'id': skill_id,
                'name': skill['name'],
                'damage_rate': skill['skillDamageRate'],
                'casting_time': skill.get('castingTime', 0),
                'sequence_length': sequence_length,
                'mana_cost': skill['manaCost']
            })

    # 버스트 시나리오: 궁극기 → 모든 스킬 → 평타
    current_time = 0.0
    total_damage = 0.0
    skill_order = []

    # 1. 궁극기 사용 (있는 경우)
    if ultimate_skill:
        ult_sequence = sum(
            m['actualDuration']
            for m in ultimate_skill.get('montageData', [])
            if 'Ready' not in m['assetName'] and 'Equipment' not in m['assetName']
        )
        ult_time = ultimate_skill.get('castingTime', 0) + ult_sequence

        if current_time + ult_time <= duration:
            ult_damage = base_damage * ultimate_skill['skillDamageRate']
            total_damage += ult_damage
            current_time += ult_time
            skill_order.append({
                'time': round(current_time, 2),
                'skill': ultimate_skill['name'],
                'damage': round(ult_damage, 2),
                'type': 'ultimate'
            })

    # 2. 모든 스킬 한 번씩 사용 (피해량 높은 순서)
    attack_skills.sort(key=lambda x: x['damage_rate'], reverse=True)

    for skill in attack_skills:
        skill_time = skill['casting_time'] + skill['sequence_length']
        if current_time + skill_time > duration:
            continue

        skill_damage = base_damage * skill['damage_rate']
        total_damage += skill_damage
        current_time += skill_time
        skill_order.append({
            'time': round(current_time, 2),
            'skill': skill['name'],
            'damage': round(skill_damage, 2),
            'type': 'skill'
        })

    # 3. 남은 시간 평타
    remaining_time = duration - current_time
    basic_result = calculate_basic_attack_dps(stalker_id, stalker_data, base_damage)
    basic_dps = basic_result['dps']
    basic_damage = basic_dps * remaining_time
    total_damage += basic_damage

    burst_dps = total_damage / duration

    return {
        'dps': round(burst_dps, 2),
        'duration': duration,
        'base_damage': round(base_damage, 2),
        'ultimate_damage': round(skill_order[0]['damage'], 2) if skill_order and skill_order[0]['type'] == 'ultimate' else 0,
        'skill_damage': round(sum(s['damage'] for s in skill_order if s['type'] == 'skill'), 2),
        'basic_damage': round(basic_damage, 2),
        'remaining_time': round(remaining_time, 2),
        'skill_order': skill_order,
        'has_ultimate': ultimate_skill is not None,
        'notes': ''
    }


def calculate_dot_dps_by_hp(target_hp_list: List[int] = [100, 500, 1000]) -> Dict:
    """DoT 스킬 DPS (대상 HP별)"""
    dot_results = {}

    for skill_id, skill_info in DOT_SKILLS.items():
        dot_type = skill_info['dot_type']
        dot_config = DOT_DAMAGE[dot_type]

        dot_results[skill_id] = {
            'stalker': skill_info['stalker'],
            'name': skill_info['name'],
            'dot_type': dot_type,
            'description': dot_config['description'],
            'dps_by_hp': {}
        }

        for target_hp in target_hp_list:
            if 'rate' in dot_config:
                # 퍼센트 피해 (Poison, Burn)
                dot_damage = target_hp * dot_config['rate']
                dot_dps = dot_damage / dot_config['duration']
            else:
                # 고정 피해 (Bleed)
                dot_damage = dot_config['damage']
                dot_dps = dot_damage / dot_config['duration']

            dot_results[skill_id]['dps_by_hp'][target_hp] = round(dot_dps, 2)

    return dot_results


def calculate_summon_independent_dps(stalker_data: Dict, base_damage: float) -> Dict:
    """소환체 독립 DPS 계산"""
    summon_results = {}

    for skill_id, summon_info in SUMMON_SKILLS.items():
        stalker_id = summon_info['stalker']

        # 해당 스토커의 스킬인지 확인
        skills = stalker_data.get('skills', {})
        if skill_id not in skills:
            continue

        skill = skills[skill_id]
        active_duration = skill.get('activeDuration', 0)

        if summon_info['summon'] == 'Ifrit':
            # Ifrit: 3개 몽타주 순차 루프 (2.87 + 2.90 + 2.52 = 8.29초 사이클)
            # 20초 지속
            montage_data = skill.get('montageData', [])
            cycle_time = sum(m['actualDuration'] for m in montage_data if 'Ready' not in m['assetName'])
            attack_count = (active_duration / cycle_time) * len(montage_data)

            # Ifrit 공격: BaseDamage × 1.2
            total_damage = base_damage * 1.2 * attack_count
            summon_dps = total_damage / active_duration if active_duration > 0 else 0

            summon_results[skill_id] = {
                'name': summon_info['name'],
                'summon': summon_info['summon'],
                'active_duration': active_duration,
                'cycle_time': round(cycle_time, 2),
                'attack_count': round(attack_count, 2),
                'dps': round(summon_dps, 2),
                'notes': f'{len(montage_data)}개 몽타주 순차 루프'
            }

        elif summon_info['summon'] == 'Shiva':
            # Shiva: 단일 몽타주 2.32초 반복
            # 60초 지속
            montage_name = summon_info.get('montage', '')
            # TODO: 실제 몽타주 시간 찾아서 계산
            cycle_time = 2.32  # 임시값
            attack_count = active_duration / cycle_time

            # Shiva 공격: BaseDamage × 0.8
            total_damage = base_damage * 0.8 * attack_count
            summon_dps = total_damage / active_duration if active_duration > 0 else 0

            summon_results[skill_id] = {
                'name': summon_info['name'],
                'summon': summon_info['summon'],
                'active_duration': active_duration,
                'cycle_time': round(cycle_time, 2),
                'attack_count': round(attack_count, 2),
                'dps': round(summon_dps, 2),
                'notes': '단일 몽타주 반복'
            }

    return summon_results


def save_dps_results_json(all_results: Dict, output_dir: Path) -> None:
    """DPS 계산 결과를 JSON으로 저장 (Claude 분석용)"""

    # 정렬된 데이터 준비
    scenario1_sorted = sorted(
        [(sid, all_results[sid]['scenario1']) for sid in STALKERS if sid in all_results],
        key=lambda x: x[1]['dps'],
        reverse=True
    )

    scenario2_sorted = sorted(
        [(sid, all_results[sid]['scenario2']) for sid in STALKERS if sid in all_results],
        key=lambda x: x[1]['dps'],
        reverse=True
    )

    scenario3_sorted = sorted(
        [(sid, all_results[sid]['scenario3']) for sid in STALKERS if sid in all_results],
        key=lambda x: x[1]['dps'],
        reverse=True
    )

    # 정렬된 순위 정보 추가
    for rank, (stalker_id, _) in enumerate(scenario1_sorted, 1):
        all_results[stalker_id]['scenario1']['rank'] = rank

    for rank, (stalker_id, _) in enumerate(scenario2_sorted, 1):
        all_results[stalker_id]['scenario2']['rank'] = rank

    for rank, (stalker_id, _) in enumerate(scenario3_sorted, 1):
        all_results[stalker_id]['scenario3']['rank'] = rank

    # JSON 저장
    output_file = output_dir / "dps_raw_results.json"

    with open(output_file, 'w', encoding='utf-8') as f:
        json.dump(all_results, f, ensure_ascii=False, indent=2)

    print(f"Generated: {output_file}")


def main():
    """메인 실행 함수"""
    print("=" * 80)
    print("DPS 시나리오 계산 v2")
    print("=" * 80)

    # 1. 출력 디렉토리 가져오기 (가장 최근 v2 폴더)
    output_dir = get_output_dir(create_new=False)
    print(f"\nOutput directory: {output_dir}")

    # 2. validated_data.json 로드
    print("\nLoading validated_data.json...")
    validated_data = load_validated_data(output_dir)
    print(f"Loaded data for {len(validated_data)} stalkers")

    # 3. 각 스토커별 DPS 계산
    print("\nCalculating DPS scenarios...")
    all_results = {}

    for stalker_id in STALKERS:
        print(f"\n  Processing: {STALKER_INFO[stalker_id]['name']} ({stalker_id})...")

        stalker_data = validated_data.get(stalker_id, {})
        if not stalker_data:
            print(f"    WARNING: No data for {stalker_id}, skipping")
            continue

        stats = stalker_data['stats']['stats']

        # BaseDamage 계산
        base_damage = calculate_base_damage(stalker_id, stats)
        print(f"    BaseDamage: {round(base_damage, 2)}")

        # 시나리오 1: 평타 DPS
        scenario1 = calculate_basic_attack_dps(stalker_id, stalker_data, base_damage)
        print(f"    Basic Attack DPS: {scenario1['dps']}")

        # 시나리오 2: 스킬 로테이션 DPS (30초)
        scenario2 = calculate_skill_rotation_dps(stalker_id, stalker_data, base_damage, 30.0)
        print(f"    Rotation DPS: {scenario2['dps']}")

        # 시나리오 3: 버스트 DPS (10초)
        scenario3 = calculate_burst_dps(stalker_id, stalker_data, base_damage, 10.0)
        print(f"    Burst DPS: {scenario3['dps']}")

        all_results[stalker_id] = {
            'scenario1': scenario1,
            'scenario2': scenario2,
            'scenario3': scenario3
        }

        # 소환체 분석 (Rene만)
        if stalker_id == 'rene':
            summon_analysis = calculate_summon_independent_dps(stalker_data, base_damage)
            all_results[stalker_id]['summon_analysis'] = summon_analysis
            print(f"    Summon analysis complete: {len(summon_analysis)} skills")

    # 4. DoT 분석 (전역)
    print("\n  Calculating DoT DPS...")
    dot_analysis = calculate_dot_dps_by_hp([100, 500, 1000])
    all_results['dot_analysis'] = dot_analysis
    print(f"    DoT analysis complete: {len(dot_analysis)} skills")

    # 5. JSON 저장 (Claude 분석용)
    print("\nSaving DPS results to JSON...")
    save_dps_results_json(all_results, output_dir)

    print("\n" + "=" * 80)
    print("DPS calculation complete!")
    print("=" * 80)
    print("\nNext step: Run Claude analysis to generate 02_DPS_시나리오_비교분석_v2.md")


if __name__ == "__main__":
    main()
