using JobTrackerApi.Services;
using Xunit;

namespace JobTrackerApi.Tests;

public sealed class JobCvMatchServiceTests
{
    private readonly JobCvMatchService _service = new();

    private static Dictionary<string, string> Sections(params (string Name, string Text)[] items)
        => items.ToDictionary(i => i.Name, i => i.Text, StringComparer.OrdinalIgnoreCase);

    public static IEnumerable<object[]> QualityFixtures()
    {
        yield return new object[]
        {
            "Norwegian",
            "Senior systemutvikler",
            "Vi ser etter deg som har erfaring med C# og ASP.NET Core. Du vil designe skalerbare distribuerte systemer. Gode samarbeidsevner er en fordel.",
            new[] { "C#", "ASP.NET Core", "designe skalerbare distribuerte systemer" },
            new[] { "med", "til", "for", "som", "erfaring", "ser", "både", "del", "dnbs", "faglig", "får", "god", "hos", "utvikling" },
        };
        yield return new object[]
        {
            "Norwegian recruitment filler",
            "Systemutvikler",
            "Hos DNBs faglige miljø får du både være del av god utvikling og bidra til moderne løsninger med C# og Azure.",
            new[] { "C#", "Azure" },
            new[] { "både", "del", "dnbs", "faglig", "får", "god", "hos", "utvikling", "bidra", "løsninger" },
        };
        yield return new object[]
        {
            "English",
            "Cloud platform engineer",
            "We need a candidate with experience in AWS and Terraform. You will lead incident response and operate distributed systems.",
            new[] { "AWS", "Terraform", "incident response", "operate distributed systems" },
            new[] { "the", "with", "experience", "candidate", "will" },
        };
        yield return new object[]
        {
            "Mixed Norwegian and English",
            "DevOps-utvikler",
            "Du vil jobbe med Node.js og CI/CD. Work closely with Azure DevOps and cross-functional product teams.",
            new[] { "Node.js", "CI/CD", "Azure DevOps", "Collaboration" },
            new[] { "og", "med", "with", "teams" },
        };
        yield return new object[]
        {
            "Short",
            "Data developer",
            "Python and SQL.",
            new[] { "Python", "SQL" },
            new[] { "and" },
        };
        yield return new object[]
        {
            "Noisy HTML",
            "Frontend developer",
            "<nav>Home Jobs Login</nav><script>trackingCookie('React')</script><main>Build accessible web applications with React and TypeScript.</main><footer>Cookie settings Privacy Terms</footer>",
            new[] { "React", "TypeScript", "Build accessible web applications" },
            new[] { "home", "jobs", "login", "cookie", "settings", "privacy", "terms", "trackingcookie" },
        };
        yield return new object[]
        {
            "Technology heavy",
            "Platform developer",
            "C++, C#, .NET, ASP.NET Core, Node.js, CI/CD, Azure DevOps and Kubernetes.",
            new[] { "C++", "C#", ".NET", "ASP.NET Core", "Node.js", "CI/CD", "Azure DevOps", "Kubernetes" },
            new[] { "and" },
        };
        yield return new object[]
        {
            "Repeated recruitment filler",
            "Software engineer",
            "Exciting opportunity for a passionate candidate. Great opportunity, strong experience required. We offer an exciting dynamic environment. Apply now. Build services using domain-driven design and Docker.",
            new[] { "domain-driven design", "Docker" },
            new[] { "exciting", "opportunity", "passionate", "candidate", "experience", "environment", "apply" },
        };
    }

    [Theory]
    [MemberData(nameof(QualityFixtures))]
    public void Quality_fixtures_keep_useful_terms_and_suppress_noise(
        string name,
        string title,
        string description,
        string[] expected,
        string[] excluded)
    {
        var result = _service.Evaluate(title, description, Sections(("Skills", "synthetic profile text")));
        var terms = result.MatchedKeywords.Concat(result.MissingKeywords).ToList();

        foreach (var term in expected)
            Assert.True(terms.Contains(term, StringComparer.OrdinalIgnoreCase), $"{name}: expected '{term}' in [{string.Join(", ", terms)}]");
        foreach (var term in excluded)
            Assert.False(terms.Contains(term, StringComparer.OrdinalIgnoreCase), $"{name}: did not expect '{term}' in [{string.Join(", ", terms)}]");
    }

    [Fact]
    public void Strong_overlap_scores_high_and_lists_matched_keywords()
    {
        var result = _service.Evaluate(
            jobTitle: "Senior C# Backend Developer",
            jobText: "We need a backend engineer with strong C#, .NET, SQL and Docker experience building REST APIs.",
            cvSections: Sections(
                ("Skills", "C# .NET SQL Docker Kubernetes"),
                ("Experience", "Built REST APIs in C# and .NET with SQL Server and Docker.")));

        Assert.True(result.Score >= 75, $"expected strong score, got {result.Score}");
        Assert.Equal("Strong", result.Band);
        Assert.Contains("C#", result.MatchedKeywords);
        Assert.Contains(".NET", result.MatchedKeywords);
        Assert.True(result.HasEnoughSignal);
    }

    [Fact]
    public void No_overlap_scores_low_and_surfaces_missing_keywords()
    {
        var result = _service.Evaluate(
            jobTitle: "Kubernetes Platform Engineer",
            jobText: "Deep Kubernetes, AWS, and Docker platform experience required. Terraform and CI/CD pipelines.",
            cvSections: Sections(
                ("Skills", "Graphic design, Adobe Photoshop, Illustrator, copywriting"),
                ("Experience", "Ran marketing campaigns and brand design work.")));

        Assert.True(result.Score < 50, $"expected low score, got {result.Score}");
        Assert.Equal("Low", result.Band);
        Assert.Contains("Kubernetes", result.MissingKeywords);
        Assert.Contains("AWS", result.MissingKeywords);
    }

    [Fact]
    public void Is_deterministic_for_identical_inputs()
    {
        var a = _service.Evaluate("Data Engineer", "Python, SQL, Spark, ETL pipelines and AWS.", Sections(("Skills", "Python SQL AWS")));
        var b = _service.Evaluate("Data Engineer", "Python, SQL, Spark, ETL pipelines and AWS.", Sections(("Skills", "Python SQL AWS")));

        Assert.Equal(a.Score, b.Score);
        Assert.Equal(a.MatchedKeywords, b.MatchedKeywords);
        Assert.Equal(a.MissingKeywords, b.MissingKeywords);
    }

    [Fact]
    public void Word_boundary_prevents_false_substring_matches()
    {
        // "go" (the language) must not match inside "goals"/"ago".
        var result = _service.Evaluate(
            jobTitle: "Go Developer",
            jobText: "Go programming language, goroutines, concurrency.",
            cvSections: Sections(("Experience", "Achieved company goals two years ago in a great environment.")));

        Assert.DoesNotContain("go", result.MatchedKeywords, StringComparer.OrdinalIgnoreCase);
    }

    [Fact]
    public void Section_coverage_reports_where_matches_are_concentrated()
    {
        var result = _service.Evaluate(
            jobTitle: "React Frontend Engineer",
            jobText: "Build UIs with React, TypeScript and JavaScript. Strong testing culture.",
            cvSections: Sections(
                ("Skills", "React TypeScript JavaScript"),
                ("Experience", "Wrote documentation and managed budgets.")));

        var skills = Assert.Single(result.SectionCoverage, s => s.Section == "Skills");
        var experience = Assert.Single(result.SectionCoverage, s => s.Section == "Experience");
        Assert.True(skills.Matched > experience.Matched);
    }

    [Fact]
    public void Empty_cv_reports_no_signal()
    {
        var result = _service.Evaluate("Anything", "Some role text with several words here.", Sections());
        Assert.False(result.HasEnoughSignal);
        Assert.Equal("Unknown", result.Band);
        Assert.Equal(0, result.MatchedCount);
    }

    [Fact]
    public void Curated_tag_matches_synonym_spelling_in_cv()
    {
        // Job posting says "Kubernetes"; CV only says "K8s" -- same skill, different spelling.
        var result = _service.Evaluate(
            jobTitle: "Platform Engineer",
            jobText: "Deep Kubernetes experience required for our platform team.",
            cvSections: Sections(("Skills", "K8s, Terraform, Helm")));

        Assert.Contains("Kubernetes", result.MatchedKeywords);
        Assert.DoesNotContain("Kubernetes", result.MissingKeywords);
    }

    [Fact]
    public void Title_keywords_are_weighted_and_missing_ones_rank_first()
    {
        // The title term "kubernetes" is absent from the CV; it should lead the missing list
        // because title terms carry the title bonus weight.
        var result = _service.Evaluate(
            jobTitle: "Kubernetes Specialist",
            jobText: "Kubernetes orchestration. Some familiarity with logging and monitoring dashboards.",
            cvSections: Sections(("Skills", "logging monitoring dashboards")));

        Assert.Equal("Kubernetes", result.MissingKeywords.First());
    }
}
