using System.Reflection;
using System.IO.Compression;
using System.Text;
using JobTrackerApi.Controllers;
using JobTrackerApi.Models;
using Microsoft.AspNetCore.Http;
using Xunit;

namespace JobTrackerApi.Tests;

// Phase 2.1-b — CV extraction coverage. The structured model and StructuredCvProfileJson.FromSections
// already map Projects, Certifications and Languages headings into the profile; the gap was upstream
// (the AI /cv/normalize prompt never emitted those headings, so the sections were dropped). These
// tests lock the C# side so that once the normalized markdown carries # Projects / # Certifications /
// # Languages, they reach the structured profile — and so a future change can't silently regress it.
//
// Content shapes mirror what the (fixed) normalizer produces for the benchmark CV
// (Connor Babbington): a Projects section, and languages stated as "Name: Level".
public sealed class CvExtractionCoverageTests
{
    private static StructuredCvSection Section(string name, string content) =>
        new() { Name = name, Content = content };

    [Fact]
    public void FromSections_maps_a_Projects_heading_into_structured_projects()
    {
        var profile = StructuredCvProfileJson.FromSections(new[]
        {
            Section("Projects",
                "JobTrack\nFull-stack job-application tracker (React, ASP.NET Core, SQLite, Docker).\n\n" +
                "InboxIntel\nGmail analytics and safe bulk-cleanup tool in .NET 8 with PostgreSQL."),
        });

        Assert.Equal(2, profile.Projects.Count);
        Assert.Contains(profile.Projects, p => p.Name == "JobTrack");
        Assert.Contains(profile.Projects, p => p.Name == "InboxIntel");
    }

    [Fact]
    public void FromSections_maps_a_Certifications_heading_into_structured_certifications()
    {
        var profile = StructuredCvProfileJson.FromSections(new[]
        {
            Section("Certifications",
                "Extended Diploma NVQ Level 3 in ICT\n\nAZ-900 Azure Fundamentals"),
        });

        Assert.NotEmpty(profile.Certifications);
        Assert.Contains(profile.Certifications, c => (c.Name ?? "").Contains("NVQ", System.StringComparison.OrdinalIgnoreCase));
    }

    [Fact]
    public void FromSections_maps_a_Languages_heading_with_levels()
    {
        var profile = StructuredCvProfileJson.FromSections(new[]
        {
            Section("Languages", "English: Native\nNorwegian: B1"),
        });

        Assert.Equal(2, profile.Languages.Count);
        Assert.Contains(profile.Languages, l => l.Name == "English" && (l.Level ?? "").Contains("Native"));
        Assert.Contains(profile.Languages, l => l.Name == "Norwegian" && (l.Level ?? "").Contains("B1"));
    }

    // The benchmark CV has all four rich sections; confirm they coexist without one clobbering another.
    [Fact]
    public void FromSections_populates_projects_certifications_and_languages_together()
    {
        var profile = StructuredCvProfileJson.FromSections(new[]
        {
            Section("Skills", "C#\n.NET\nDocker"),
            Section("Projects", "JobTrack\nJob-application tracker."),
            Section("Certifications", "NVQ Level 3 in ICT"),
            Section("Languages", "English: Native\nNorwegian: B1"),
        });

        Assert.NotEmpty(profile.Skills);
        Assert.Single(profile.Projects);
        Assert.NotEmpty(profile.Certifications);
        Assert.Equal(2, profile.Languages.Count);
    }
    [Fact]
    public void Normalized_markdown_strips_skill_group_labels()
    {
        var profile = InvokeProfileBuilder("""
            # Skills
            Development: C#
            DevOps & Infrastructure: Docker
            Practices: CI/CD
            """);

        Assert.Equal(new[] { "C#", "CI/CD", "Docker" }, profile.Skills);
        Assert.DoesNotContain(profile.Skills, skill => skill.StartsWith("Development", System.StringComparison.OrdinalIgnoreCase));
    }

    [Fact]
    public void Normalized_markdown_separates_glued_dates_from_job_title()
    {
        var profile = InvokeProfileBuilder("""
            # Work Experience
            2015–2023System Developer
            Warwickshire County Council, UK
            - Built APIs
            """);

        var job = Assert.Single(profile.Jobs);
        Assert.Equal("2015", job.Start);
        Assert.Equal("2023", job.End);
        Assert.Contains("System Developer", job.Title ?? string.Empty);
    }

    [Fact]
    public void Extraction_repairs_common_utf8_as_latin1_mojibake()
    {
        var method = typeof(ProfileCvController).GetMethod("RepairKnownMojibake", BindingFlags.NonPublic | BindingFlags.Static)!;
        var repaired = Assert.IsType<string>(method.Invoke(null, new object[] { "TÃ¸nsberg 2015â€“2023" }));

        Assert.Equal("Tønsberg 2015–2023", repaired);
    }

    [Fact]
    public void Earlier_part_time_roles_become_separate_experience_entries()
    {
        var profile = InvokeProfileBuilder("""
            # Work Experience
            System Developer
            Warwickshire County Council
            2015–2023
            - Built APIs

            Earlier roles (part-time)
            - Sales Assistant — Royal Vapes | 2013–2015
            - Labourer — The Hodcarrier | 2014–2016
            - Lifeguard — Nuffield Health | 2012–2014
            """);

        Assert.Contains(profile.Jobs, job => job.Title == "Sales Assistant" && job.Company == "Royal Vapes");
        Assert.Contains(profile.Jobs, job => job.Title == "Labourer" && job.Company == "The Hodcarrier");
        Assert.Contains(profile.Jobs, job => job.Title == "Lifeguard" && job.Company == "Nuffield Health");
        Assert.Equal(4, profile.Jobs.Count);
    }

    [Fact]
    public void Numbered_editorial_headings_are_canonicalized_without_personal_fixture_data()
    {
        var profile = InvokeHeuristicBuilder("""
            01 — PROFESSIONAL SUMMARY
            Backend engineer focused on reliable services and clear delivery.

            02 — CORE COMPETENCIES
            C#
            Docker

            03 — PROFESSIONAL EXPERIENCE
            Software Engineer
            Example Council
            2020–Present
            - Built maintainable APIs
            """);

        Assert.NotEmpty(profile.Summary);
        Assert.Contains("C#", profile.Skills);
        Assert.Contains(profile.Jobs, job => job.Title == "Software Engineer");
    }

    [Fact]
    public void Unknown_markdown_sections_are_preserved_for_import_review()
    {
        var profile = InvokeHeuristicBuilder("""
            # Professional Summary
            Engineer with broad delivery experience.

            # Community Leadership
            Mentored early-career developers
            Organised a monthly meetup
            """);

        var custom = Assert.Single(profile.OtherSections, section => section.Title == "Community Leadership");
        Assert.Contains(custom.Items, item => item.Contains("Mentored", System.StringComparison.Ordinal));
    }

    [Fact]
    public void Contact_detection_uses_the_location_segment_and_rejects_section_headings()
    {
        var profile = InvokeHeuristicBuilder("""
            Alex Example
            Platform Engineer
            Tønsberg, Norway  ·  +47 412 34 567  ·  alex.example@example.test
            portfolio.example.dev  ·  linkedin.com/in/alex-example
            Core Competencies
            Backend Development · System Design
            """);

        Assert.Equal("Tønsberg, Norway", profile.Contact.Location);
        Assert.Equal("portfolio.example.dev", profile.Contact.Website);
    }

    [Fact]
    public void Norwegian_section_headings_are_canonicalized()
    {
        var profile = InvokeHeuristicBuilder("""
            Sammendrag
            Utvikler med erfaring fra robuste tjenester.

            Teknisk kompetanse
            C#
            Docker

            Arbeidserfaring
            Systemutvikler
            Eksempelkommune
            2020–Present
            - Bygget pålitelige tjenester

            Utdanning
            Bachelor i informatikk
            Eksempelhøgskolen
            2017–2020

            Språk
            Norsk: B2
            English: Native
            """);

        Assert.NotEmpty(profile.Summary);
        Assert.Contains("C#", profile.Skills);
        Assert.NotEmpty(profile.Jobs);
        Assert.NotEmpty(profile.Education);
        Assert.Equal(2, profile.Languages.Count);
    }

    [Fact]
    public void Norwegian_inline_language_pairs_keep_their_own_proficiency()
    {
        var profile = InvokeHeuristicBuilder("""
            Språk
            Engelsk — morsmål — Norsk — A2/B1, under aktiv utvikling mot B2
            """);

        Assert.Contains(profile.Languages, language => language.Name == "English" && language.Level == "Native");
        Assert.Contains(profile.Languages, language => language.Name == "Norwegian" && language.Level == "A2/B1");
    }

    [Fact]
    public async Task Binary_document_fallback_is_disabled()
    {
        await using var package = new MemoryStream();
        using (var archive = new ZipArchive(package, ZipArchiveMode.Create, leaveOpen: true))
        {
            var entry = archive.CreateEntry("word/document.xml");
            await using var stream = entry.Open();
            await using var writer = new StreamWriter(stream);
            await writer.WriteAsync("""
                <w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"><w:body>
                  <w:p><w:r><w:t>Technical Skills</w:t></w:r></w:p>
                  <w:tbl>
                    <w:tr><w:tc><w:p><w:r><w:t>Backend</w:t></w:r></w:p></w:tc><w:tc><w:p><w:r><w:t>C#, .NET</w:t></w:r></w:p></w:tc></w:tr>
                    <w:tr><w:tc><w:p><w:r><w:t>DevOps</w:t></w:r></w:p></w:tc><w:tc><w:p><w:r><w:t>Docker, Linux</w:t></w:r></w:p></w:tc></w:tr>
                  </w:tbl>
                  <w:p><w:pPr><w:pStyle w:val="CVRole" /></w:pPr><w:r><w:t>Example Engineer</w:t></w:r></w:p>
                  <w:p><w:pPr><w:pStyle w:val="CVBullet" /></w:pPr><w:r><w:t>Built reliable services</w:t></w:r></w:p>
                </w:body></w:document>
                """);
        }
        package.Position = 0;
        var upload = new FormFile(package, 0, package.Length, "file", "sanitized.docx");
        var method = typeof(ProfileCvController).GetMethod("ExtractPlainTextAsync", BindingFlags.NonPublic | BindingFlags.Static)!;
        var task = Assert.IsAssignableFrom<Task<string>>(method.Invoke(null, new object[] { upload, ".docx", CancellationToken.None }));
        var extracted = await task;

        Assert.Empty(extracted);
    }

    [Fact]
    public async Task Plain_text_fallback_reads_bounded_utf8()
    {
        const string source = "# Ada Lovelace\n\n## Skills\nC#\nSQL";
        await using var stream = new MemoryStream(Encoding.UTF8.GetBytes(source));
        var upload = new FormFile(stream, 0, stream.Length, "file", "resume.md");
        var method = typeof(ProfileCvController).GetMethod("ExtractPlainTextAsync", BindingFlags.NonPublic | BindingFlags.Static)!;
        var task = Assert.IsAssignableFrom<Task<string>>(method.Invoke(null, new object[] { upload, ".md", CancellationToken.None }));

        Assert.Equal(source, await task);
    }

    private static StructuredCvProfile InvokeProfileBuilder(string markdown)
    {
        var method = typeof(ProfileCvController).GetMethod("BuildStructuredCvFromNormalizedMarkdown", BindingFlags.NonPublic | BindingFlags.Static)!;
        return Assert.IsType<StructuredCvProfile>(method.Invoke(null, new object[] { markdown }));
    }

    private static StructuredCvProfile InvokeHeuristicBuilder(string text)
    {
        var method = typeof(ProfileCvController).GetMethod("BuildHeuristicStructuredCv", BindingFlags.NonPublic | BindingFlags.Static)!;
        return Assert.IsType<StructuredCvProfile>(method.Invoke(null, new object[] { text, text }));
    }

}
