using FluentAssertions;
using AxCopilot.Core;
using Xunit;

namespace AxCopilot.Tests.Core;

public class FuzzyEngineTests
{
    // ─── CalculateScore 기본 매칭 ────────────────────────────────────────────

    [Fact]
    public void CalculateScore_ExactMatch_ReturnsHighestScore()
    {
        var score = FuzzyEngine.CalculateScore("notepad", "notepad", 0);
        score.Should().BeGreaterThanOrEqualTo(1000);
    }

    [Fact]
    public void CalculateScore_PrefixMatch_ReturnsHighScore()
    {
        var score = FuzzyEngine.CalculateScore("note", "notepad", 0);
        score.Should().BeGreaterThanOrEqualTo(800);
        score.Should().BeLessThan(1000);
    }

    [Fact]
    public void CalculateScore_ContainsMatch_ReturnsMediumScore()
    {
        var score = FuzzyEngine.CalculateScore("pad", "notepad", 0);
        score.Should().BeGreaterThanOrEqualTo(600);
        score.Should().BeLessThan(800);
    }

    [Fact]
    public void CalculateScore_NoMatch_ReturnsZero()
    {
        var score = FuzzyEngine.CalculateScore("xyz", "notepad", 0);
        score.Should().Be(0);
    }

    [Fact]
    public void CalculateScore_EmptyQuery_ReturnsZero()
    {
        var score = FuzzyEngine.CalculateScore("", "notepad", 0);
        score.Should().Be(0);
    }

    [Fact]
    public void CalculateScore_BaseScore_AddsToResult()
    {
        var scoreWithBase = FuzzyEngine.CalculateScore("notepad", "notepad", 100);
        var scoreWithout = FuzzyEngine.CalculateScore("notepad", "notepad", 0);
        scoreWithBase.Should().Be(scoreWithout + 100);
    }

    [Fact]
    public void CalculateScore_ExactBeforePrefix()
    {
        var exact = FuzzyEngine.CalculateScore("note", "note", 0);
        var prefix = FuzzyEngine.CalculateScore("not", "note", 0);
        exact.Should().BeGreaterThan(prefix);
    }

    // ─── FuzzyMatch ──────────────────────────────────────────────────────────

    [Fact]
    public void FuzzyMatch_AllCharsPresent_ReturnsPositive()
    {
        var score = FuzzyEngine.FuzzyMatch("ntpd", "notepad");
        score.Should().BePositive();
    }

    [Fact]
    public void FuzzyMatch_CharsMissing_ReturnsZero()
    {
        var score = FuzzyEngine.FuzzyMatch("xyz", "notepad");
        score.Should().Be(0);
    }

    [Fact]
    public void FuzzyMatch_ConsecutiveCharsScoreHigher()
    {
        var consecutive = FuzzyEngine.FuzzyMatch("not", "notepad");
        var scattered = FuzzyEngine.FuzzyMatch("ntp", "notepad");
        consecutive.Should().BeGreaterThan(scattered);
    }

    [Fact]
    public void FuzzyMatch_EmptyQuery_ReturnsPositive()
    {
        var score = FuzzyEngine.FuzzyMatch("", "notepad");
        score.Should().BeGreaterThanOrEqualTo(0);
    }

    [Fact]
    public void FuzzyMatch_FullMatch_ReturnsHighScore()
    {
        var full = FuzzyEngine.FuzzyMatch("abcde", "abcde");
        var partial = FuzzyEngine.FuzzyMatch("ace", "abcde");
        full.Should().BeGreaterThan(partial);
    }

    [Fact]
    public void FuzzyMatch_MinimumScoreGuaranteed()
    {
        var score = FuzzyEngine.FuzzyMatch("ntpd", "notepad");
        score.Should().BeGreaterThanOrEqualTo(50);
    }

    // ─── 한글 자모 분리 ─────────────────────────────────────────────────────

    [Theory]
    [InlineData("가", "ㄱㅏ")]
    [InlineData("한", "ㅎㅏㄴ")]
    [InlineData("글", "ㄱㅡㄹ")]
    [InlineData("abc", "abc")]
    [InlineData("가a나", "ㄱㅏaㄴㅏ")]
    public void DecomposeToJamo_ReturnsCorrectJamo(string input, string expected)
    {
        FuzzyEngine.DecomposeToJamo(input).Should().Be(expected);
    }

    // ─── 자모 기반 포함 검색 ────────────────────────────────────────────────

    [Fact]
    public void JamoContainsScore_MiddleWord_ReturnsPositive()
    {
        // "모장" → "메모장" (자모 분리 후 연속 매칭)
        var score = FuzzyEngine.JamoContainsScore("메모장", "모장");
        score.Should().BePositive();
    }

    [Fact]
    public void JamoContainsScore_NoMatch_ReturnsZero()
    {
        var score = FuzzyEngine.JamoContainsScore("메모장", "가나");
        score.Should().Be(0);
    }

    [Fact]
    public void JamoContainsScore_SubsequenceMatch_ReturnsPositive()
    {
        // "메장" → 메-모-장에서 비연속 자모 매칭
        var score = FuzzyEngine.JamoContainsScore("메모장", "메장");
        score.Should().BePositive();
    }

    [Fact]
    public void JamoContainsScore_NonKorean_ReturnsZero()
    {
        var score = FuzzyEngine.JamoContainsScore("notepad", "pad");
        score.Should().Be(0); // 영어는 Contains에서 이미 처리
    }

    // ─── 한글 초성 검색 ──────────────────────────────────────────────────────

    [Theory]
    [InlineData("ㄴ", true)]
    [InlineData("ㄴㅌ", true)]
    [InlineData("ㄱㄴㄷ", true)]
    public void IsChosung_ValidChosung_ReturnsTrue(string text, bool expected)
    {
        FuzzyEngine.IsChosung(text).Should().Be(expected);
    }

    [Theory]
    [InlineData("notepad", false)]
    [InlineData("노트패드", false)]
    [InlineData("a", false)]
    [InlineData("ㄴa", false)]
    public void IsChosung_NonChosung_ReturnsFalse(string text, bool expected)
    {
        FuzzyEngine.IsChosung(text).Should().Be(expected);
    }

    [Fact]
    public void GetChosung_HangulChar_ReturnsCorrectChosung()
    {
        FuzzyEngine.GetChosung('나').Should().Be('ㄴ');
        FuzzyEngine.GetChosung('가').Should().Be('ㄱ');
        FuzzyEngine.GetChosung('하').Should().Be('ㅎ');
    }

    [Fact]
    public void GetChosung_NonHangul_ReturnsNull()
    {
        FuzzyEngine.GetChosung('a').Should().Be('\0');
        FuzzyEngine.GetChosung('1').Should().Be('\0');
    }

    [Fact]
    public void ContainsChosung_ConsecutiveMatch_ReturnsTrue()
    {
        FuzzyEngine.ContainsChosung("노트패드", "ㄴㅌ").Should().BeTrue();
    }

    [Fact]
    public void ContainsChosung_NonMatchingChosung_ReturnsFalse()
    {
        FuzzyEngine.ContainsChosung("노트패드", "ㅅ").Should().BeFalse();
    }

    [Fact]
    public void ContainsChosung_PartialMatch_ReturnsTrue()
    {
        FuzzyEngine.ContainsChosung("계산기", "ㄱㅅ").Should().BeTrue();
    }

    [Fact]
    public void ContainsChosung_QueryLongerThanTarget_ReturnsFalse()
    {
        FuzzyEngine.ContainsChosung("가", "ㄱㄴㄷㄹ").Should().BeFalse();
    }

    [Fact]
    public void ContainsChosung_NonConsecutive_ReturnsTrue()
    {
        // "ㅁㅊ" → 메모장(ㅁㅁㅈ) — 안 맞음 (ㅊ가 없으므로)
        FuzzyEngine.ContainsChosung("메모장", "ㅁㅊ").Should().BeFalse();

        // "ㅁㅈ" → 메모장(ㅁㅁㅈ) — 비연속 매칭
        FuzzyEngine.ContainsChosung("메모장", "ㅁㅈ").Should().BeTrue();
    }

    // ─── 초성 점수 매칭 ─────────────────────────────────────────────────────

    [Fact]
    public void ChosungMatchScore_PureChosung_Consecutive()
    {
        var score = FuzzyEngine.ChosungMatchScore("계산기", "ㄱㅅ");
        score.Should().BeGreaterThanOrEqualTo(500);
    }

    [Fact]
    public void ChosungMatchScore_PureChosung_Subsequence()
    {
        // "ㅁㅈ" → 메모장 (ㅁ...ㅈ 비연속)
        var score = FuzzyEngine.ChosungMatchScore("메모장", "ㅁㅈ");
        score.Should().BePositive();
    }

    [Fact]
    public void ChosungMatchScore_MixedQuery()
    {
        // "ㅁ장" → 혼합: ㅁ은 초성, 장은 완성형
        var score = FuzzyEngine.ChosungMatchScore("메모장", "ㅁ장");
        score.Should().BePositive();
    }

    [Fact]
    public void ChosungMatchScore_NoMatch_ReturnsZero()
    {
        var score = FuzzyEngine.ChosungMatchScore("메모장", "ㅋㅋ");
        score.Should().Be(0);
    }

    // ─── 통합 점수 우선순위 ─────────────────────────────────────────────────

    [Fact]
    public void CalculateScore_ScoreHierarchy()
    {
        // 정확 > 시작 > 포함 > 자모포함 > 초성 > fuzzy
        var exact = FuzzyEngine.CalculateScore("메모장", "메모장", 0);
        var prefix = FuzzyEngine.CalculateScore("메모", "메모장", 0);
        var contains = FuzzyEngine.CalculateScore("모장", "메모장", 0);

        exact.Should().BeGreaterThan(prefix);
        prefix.Should().BeGreaterThan(contains);
    }

    [Fact]
    public void CalculateScore_JamoBeforeChosung()
    {
        // "모장" (자모 포함) > "ㅁㅈ" (초성 비연속)
        var jamo = FuzzyEngine.CalculateScore("모장", "메모장", 0);
        var chosung = FuzzyEngine.CalculateScore("ㅁㅈ", "메모장", 0);
        jamo.Should().BeGreaterThan(chosung);
    }
}
