using System.Linq;
using NUnit.Framework;
using UnityEngine;
using UnityEditor;

/// <summary>
/// EerieVillage BT5-Dev 2단계 — Player 근거리 공격 체계 EditMode 테스트.
/// Prefab 자산의 컴포넌트 구성이 기획 04 §5-1 (근거리 공격 1종) 을 충족하는지 검증.
/// Play 모드 실행 불요 — prefab YAML 직렬화 상태를 직접 검증하여 회귀 방지.
///
/// 2026-04-23 개정: Platformer.* 네임스페이스 직접 참조 제거 (Scripts/ 하위에 asmdef 부재로
/// 테스트 어셈블리가 Assembly-CSharp 를 참조 불가한 구조 — reflection 기반으로 전환).
/// </summary>
public class PlayerAttackTests
{
    const string PlayerPrefabPath = "Assets/Prefabs/Player.prefab";
    const string EnemyPrefabPath = "Assets/Prefabs/Enemy.prefab";

    // Platformer.* 는 Assembly-CSharp 에 속함. 테스트 어셈블리에서 직접 타입 참조 불가하므로
    // GetComponents<Component>() + GetType().FullName 매칭으로 검증.
    const string AttackHitboxType = "Platformer.Mechanics.AttackHitbox";
    const string HealthType = "Platformer.Mechanics.Health";
    const string PlayerControllerType = "Platformer.Mechanics.PlayerController";
    const string EnemyControllerType = "Platformer.Mechanics.EnemyController";

    static Component FindComponentByFullName(GameObject go, string fullName)
    {
        if (go == null) return null;
        return go.GetComponents<Component>()
            .FirstOrDefault(c => c != null && c.GetType().FullName == fullName);
    }

    static object GetFieldOrProperty(object obj, string memberName)
    {
        if (obj == null) return null;
        var t = obj.GetType();
        var field = t.GetField(memberName,
            System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
        if (field != null) return field.GetValue(obj);
        var prop = t.GetProperty(memberName,
            System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
        return prop?.GetValue(obj);
    }

    [Test]
    public void Player_Prefab_Has_AttackHitbox_Component()
    {
        var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(PlayerPrefabPath);
        Assert.IsNotNull(prefab, $"Player.prefab not found at {PlayerPrefabPath}");

        var hitbox = FindComponentByFullName(prefab, AttackHitboxType);
        Assert.IsNotNull(hitbox,
            "Player.prefab에 AttackHitbox 컴포넌트가 누락. " +
            "BT5-Dev 2단계 재위임 집행분 (2026-04-23) 이 prefab YAML 에 반영되어야 함.");
    }

    [Test]
    public void Player_Prefab_Has_Health_Component()
    {
        var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(PlayerPrefabPath);
        Assert.IsNotNull(prefab, $"Player.prefab not found at {PlayerPrefabPath}");

        var health = FindComponentByFullName(prefab, HealthType);
        Assert.IsNotNull(health, "Player.prefab 에 Health 컴포넌트 누락 (템플릿 기본).");
    }

    [Test]
    public void Player_Prefab_Has_PlayerController_Component()
    {
        var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(PlayerPrefabPath);
        Assert.IsNotNull(prefab);

        var controller = FindComponentByFullName(prefab, PlayerControllerType);
        Assert.IsNotNull(controller, "Player.prefab 에 PlayerController 컴포넌트 누락.");
    }

    [Test]
    public void AttackHitbox_Default_Damage_Is_One()
    {
        var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(PlayerPrefabPath);
        var hitbox = FindComponentByFullName(prefab, AttackHitboxType);
        Assert.IsNotNull(hitbox);

        var damage = GetFieldOrProperty(hitbox, "damage");
        Assert.IsNotNull(damage, "AttackHitbox.damage 필드/프로퍼티 접근 불가");
        Assert.AreEqual(1, System.Convert.ToInt32(damage),
            "기본 대미지 1 (기획 04 §5-1, Phase 3-B 튠 전 파일럿 값).");
    }

    [Test]
    public void AttackHitbox_Active_Duration_Is_Positive()
    {
        var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(PlayerPrefabPath);
        var hitbox = FindComponentByFullName(prefab, AttackHitboxType);
        Assert.IsNotNull(hitbox);

        var duration = GetFieldOrProperty(hitbox, "activeDuration");
        Assert.IsNotNull(duration, "AttackHitbox.activeDuration 필드/프로퍼티 접근 불가");
        Assert.Greater(System.Convert.ToSingle(duration), 0f,
            "activeDuration 가 0 이하면 OverlapBox 판정이 즉시 종료되어 공격 무효.");
    }

    [Test]
    public void Enemy_Prefab_Has_Health_Component()
    {
        var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(EnemyPrefabPath);
        Assert.IsNotNull(prefab, $"Enemy.prefab not found at {EnemyPrefabPath}");

        var health = FindComponentByFullName(prefab, HealthType);
        Assert.IsNotNull(health,
            "Enemy.prefab 에 Health 컴포넌트 누락. " +
            "BT5-Dev 2단계 재위임 집행분 (2026-04-23) 이 prefab YAML 에 반영되어야 함. " +
            "Health 없으면 AttackHitbox.Update 의 Decrement 호출이 불가 → EnemyDeath 체인 미발동.");
    }

    [Test]
    public void Enemy_Prefab_Health_MaxHP_Is_One()
    {
        var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(EnemyPrefabPath);
        var health = FindComponentByFullName(prefab, HealthType);
        Assert.IsNotNull(health);

        var maxHP = GetFieldOrProperty(health, "maxHP");
        Assert.IsNotNull(maxHP, "Health.maxHP 필드/프로퍼티 접근 불가");
        Assert.AreEqual(1, System.Convert.ToInt32(maxHP),
            "일반 적 기본 maxHP 1 (코어 룰 7 정합, 첫 세팅).");
    }

    [Test]
    public void Enemy_Prefab_Has_EnemyController_Component()
    {
        var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(EnemyPrefabPath);
        var controller = FindComponentByFullName(prefab, EnemyControllerType);
        Assert.IsNotNull(controller,
            "EnemyDeath 체인에 EnemyController 필수 (AttackHitbox.Update 에서 Schedule<EnemyDeath>().enemy = enemy).");
    }

    // ===== BT5-Dev 3단계 PlayerTestGirl 아틀라스 적용 검증 (2026-04-24) =====

    const string PlayerTestGirlGuid = "44ad58ba82191ca4d818108ab01d3baa";

    [Test]
    public void Player_Prefab_SpriteRenderer_References_PlayerTestGirl()
    {
        // Player.prefab YAML 직접 파싱 — SpriteRenderer.m_Sprite 의 guid 가
        // PlayerTestGirl.png.meta 의 guid 와 일치하는지 검증. 기존 PlayerIdle(
        // ba86c7b200abe499cb750833482830b3) 에서 PlayerTestGirl 로 교체되었는지 회귀 확인.
        var path = System.IO.Path.GetFullPath(PlayerPrefabPath);
        Assert.IsTrue(System.IO.File.Exists(path), $"Player.prefab 부재 — {path}");
        var yaml = System.IO.File.ReadAllText(path);

        // m_Sprite: {fileID: ..., guid: <GUID>, type: 3}
        var match = System.Text.RegularExpressions.Regex.Match(
            yaml,
            @"m_Sprite:\s*\{fileID:\s*[-0-9]+,\s*guid:\s*([a-f0-9]{32}),\s*type:\s*3\}");
        Assert.IsTrue(match.Success,
            "Player.prefab YAML 에서 SpriteRenderer.m_Sprite guid 추출 실패.");
        Assert.AreEqual(PlayerTestGirlGuid, match.Groups[1].Value,
            $"Player.prefab SpriteRenderer.m_Sprite guid 가 PlayerTestGirl({PlayerTestGirlGuid}) 이어야 함. 실제: {match.Groups[1].Value}");
    }

    [Test]
    public void Player_Controller_Has_Attack_Parameter_And_State()
    {
        // Player.controller YAML 직접 파싱 — attack Trigger 파라미터 + Player-Attack State 존재 검증.
        // BT5-Dev 3단계에서 PlayerAttack.cs 의 Schedule<PlayerAttack> 과 연동되는 State Machine 요소가
        // controller 에 명시되었는지 회귀 확인.
        var path = System.IO.Path.GetFullPath("Assets/Character/Animations/Player.controller");
        Assert.IsTrue(System.IO.File.Exists(path), $"Player.controller 부재 — {path}");
        var yaml = System.IO.File.ReadAllText(path);

        // m_Name: attack 파라미터 + m_Type: 9 (Trigger)
        Assert.IsTrue(
            System.Text.RegularExpressions.Regex.IsMatch(
                yaml,
                @"m_Name:\s*attack\s*\r?\n\s*m_Type:\s*9"),
            "Player.controller 에 attack Trigger 파라미터(m_Type:9) 누락. " +
            "BT5-Dev 3단계 PlayerAttack State 연동 전제.");

        // Player-Attack State 존재
        Assert.IsTrue(
            yaml.Contains("m_Name: Player-Attack"),
            "Player.controller 에 Player-Attack State 누락. " +
            "PlayerAttack.anim(guid c8d7e5a1f9b24e63a7f5d2c8e1b9a4f7) 모션을 호스트하는 State 가 필요.");
    }
}
