using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Commons.Csv;

namespace ZB.MOM.WW.OtOpcUa.Commons.Tests.Csv;

/// <summary>
///     The authoritative RFC 4180 read corpus for <see cref="CsvParser"/>. Every edge the RFC pins —
///     quoting, embedded delimiters/newlines, escaped quotes, space preservation, empty vs. absent
///     fields, trailing-newline handling, CRLF/LF/CR terminators — plus this parser's declared
///     <b>strict</b> malformed-input and <b>faithful</b> empty-line policies, lives here.
/// </summary>
public sealed class CsvParserTests
{
    // ---- simple rows ----

    [Fact]
    public void Parses_simple_rows()
    {
        var rows = CsvParser.Parse("a,b,c\nd,e,f");

        rows.Count.ShouldBe(2);
        rows[0].ShouldBe(new[] { "a", "b", "c" });
        rows[1].ShouldBe(new[] { "d", "e", "f" });
    }

    [Fact]
    public void Single_field_single_row()
    {
        CsvParser.Parse("hello").ShouldBe(new[] { new[] { "hello" } });
    }

    [Fact]
    public void Empty_input_yields_no_rows()
    {
        CsvParser.Parse("").Count.ShouldBe(0);
        CsvParser.Parse((string?)null).Count.ShouldBe(0);
    }

    // ---- empty vs. absent fields ----

    [Fact]
    public void Middle_empty_field_is_preserved()
    {
        CsvParser.Parse("a,,c").Single().ShouldBe(new[] { "a", "", "c" });
    }

    [Fact]
    public void Trailing_comma_yields_trailing_empty_field()
    {
        CsvParser.Parse("a,").Single().ShouldBe(new[] { "a", "" });
    }

    [Fact]
    public void Leading_comma_yields_leading_empty_field()
    {
        CsvParser.Parse(",a").Single().ShouldBe(new[] { "", "a" });
    }

    [Fact]
    public void All_empty_fields()
    {
        CsvParser.Parse(",,").Single().ShouldBe(new[] { "", "", "" });
    }

    // ---- quoting ----

    [Fact]
    public void Quoted_field_with_embedded_comma()
    {
        CsvParser.Parse("\"a,b\",c").Single().ShouldBe(new[] { "a,b", "c" });
    }

    [Fact]
    public void Quoted_field_with_embedded_lf()
    {
        CsvParser.Parse("\"line1\nline2\",b").Single().ShouldBe(new[] { "line1\nline2", "b" });
    }

    [Fact]
    public void Quoted_field_with_embedded_crlf()
    {
        CsvParser.Parse("\"line1\r\nline2\",b").Single().ShouldBe(new[] { "line1\r\nline2", "b" });
    }

    [Fact]
    public void Escaped_quote_becomes_single_quote()
    {
        // "She said ""hi""" -> She said "hi"
        CsvParser.Parse("\"She said \"\"hi\"\"\"").Single().ShouldBe(new[] { "She said \"hi\"" });
    }

    [Fact]
    public void Empty_quoted_field_is_empty_string()
    {
        CsvParser.Parse("\"\",\"\"").Single().ShouldBe(new[] { "", "" });
    }

    [Fact]
    public void Quoted_field_containing_only_escaped_quotes()
    {
        // """" -> a single literal double-quote
        CsvParser.Parse("\"\"\"\"").Single().ShouldBe(new[] { "\"" });
    }

    [Fact]
    public void Quoted_field_at_end_of_row_then_more_rows()
    {
        var rows = CsvParser.Parse("\"a,b\"\nc,d");
        rows.Count.ShouldBe(2);
        rows[0].ShouldBe(new[] { "a,b" });
        rows[1].ShouldBe(new[] { "c", "d" });
    }

    // ---- space preservation (RFC 4180 §2.4) ----

    [Fact]
    public void Spaces_outside_quotes_are_preserved()
    {
        CsvParser.Parse(" a , b ").Single().ShouldBe(new[] { " a ", " b " });
    }

    [Fact]
    public void Spaces_inside_quotes_are_preserved()
    {
        CsvParser.Parse("\"  padded  \"").Single().ShouldBe(new[] { "  padded  " });
    }

    // ---- line endings ----

    [Fact]
    public void Lf_line_endings()
    {
        CsvParser.Parse("a\nb\nc").Select(r => r[0]).ShouldBe(new[] { "a", "b", "c" });
    }

    [Fact]
    public void Crlf_line_endings()
    {
        CsvParser.Parse("a\r\nb\r\nc").Select(r => r[0]).ShouldBe(new[] { "a", "b", "c" });
    }

    [Fact]
    public void Bare_cr_line_endings()
    {
        CsvParser.Parse("a\rb\rc").Select(r => r[0]).ShouldBe(new[] { "a", "b", "c" });
    }

    // ---- trailing newline: present vs. absent ----

    [Fact]
    public void Trailing_lf_does_not_add_phantom_row()
    {
        CsvParser.Parse("a,b\n").Count.ShouldBe(1);
        CsvParser.Parse("a,b\n").Single().ShouldBe(new[] { "a", "b" });
    }

    [Fact]
    public void Trailing_crlf_does_not_add_phantom_row()
    {
        CsvParser.Parse("a,b\r\n").Count.ShouldBe(1);
    }

    [Fact]
    public void No_trailing_newline_keeps_last_row()
    {
        var withNl = CsvParser.Parse("a\nb\n");
        var withoutNl = CsvParser.Parse("a\nb");
        withNl.Count.ShouldBe(2);
        withoutNl.Count.ShouldBe(2);
        withNl[1].ShouldBe(withoutNl[1]);
    }

    [Fact]
    public void Quoted_field_with_trailing_newline_no_phantom_row()
    {
        var rows = CsvParser.Parse("\"a\"\n");
        rows.Count.ShouldBe(1);
        rows[0].ShouldBe(new[] { "a" });
    }

    // ---- empty-line policy (faithful RFC: empty line == one empty field) ----

    [Fact]
    public void Empty_line_is_a_single_empty_field()
    {
        // a\n\nb  ->  ["a"], [""], ["b"]  — the blank line is NOT dropped.
        var rows = CsvParser.Parse("a\n\nb");
        rows.Count.ShouldBe(3);
        rows[0].ShouldBe(new[] { "a" });
        rows[1].ShouldBe(new[] { "" });
        rows[2].ShouldBe(new[] { "b" });
    }

    [Fact]
    public void Callers_can_filter_blank_lines()
    {
        var rows = CsvParser.Parse("a\n\nb")
            .Where(r => r.Length > 1 || r[0].Length > 0)
            .ToList();
        rows.Count.ShouldBe(2);
    }

    // ---- streaming overload ----

    [Fact]
    public void Streams_over_a_textreader()
    {
        using var reader = new StringReader("x,y\nz,w");
        var rows = CsvParser.Parse(reader).ToList();
        rows.Count.ShouldBe(2);
        rows[1].ShouldBe(new[] { "z", "w" });
    }

    [Fact]
    public void Custom_delimiter_semicolon()
    {
        CsvParser.Parse("a;b;c", ';').Single().ShouldBe(new[] { "a", "b", "c" });
    }

    [Fact]
    public void Custom_delimiter_leaves_comma_literal()
    {
        CsvParser.Parse("a,b;c", ';').Single().ShouldBe(new[] { "a,b", "c" });
    }

    // ---- strict malformed-input policy ----

    [Fact]
    public void Unterminated_quote_throws()
    {
        var ex = Should.Throw<FormatException>(() => CsvParser.Parse("\"unclosed"));
        ex.Message.ShouldContain("Unterminated");
    }

    [Fact]
    public void Quote_inside_unquoted_field_throws()
    {
        var ex = Should.Throw<FormatException>(() => CsvParser.Parse("ab\"c"));
        ex.Message.ShouldContain("unquoted");
    }

    [Fact]
    public void Character_after_closing_quote_throws()
    {
        var ex = Should.Throw<FormatException>(() => CsvParser.Parse("\"ab\"c"));
        ex.Message.ShouldContain("after closing quote");
    }

    [Fact]
    public void Malformed_message_carries_position()
    {
        var ex = Should.Throw<FormatException>(() => CsvParser.Parse("a,b\nab\"c"));
        ex.Message.ShouldContain("line 2");
    }

    [Fact]
    public void Delimiter_may_not_be_a_quote()
    {
        Should.Throw<ArgumentException>(() => CsvParser.Parse("a,b", '"').ToString());
    }

    // ---- ParseWithHeader ----

    [Fact]
    public void ParseWithHeader_maps_rows_by_name()
    {
        var rows = CsvParser.ParseWithHeader("name,age\nalice,30\nbob,40");
        rows.Count.ShouldBe(2);
        rows[0]["name"].ShouldBe("alice");
        rows[0]["age"].ShouldBe("30");
        rows[1]["name"].ShouldBe("bob");
    }

    [Fact]
    public void ParseWithHeader_empty_document_yields_no_rows()
    {
        CsvParser.ParseWithHeader("").Count.ShouldBe(0);
    }

    [Fact]
    public void ParseWithHeader_header_only_yields_no_rows()
    {
        CsvParser.ParseWithHeader("name,age").Count.ShouldBe(0);
    }

    [Fact]
    public void ParseWithHeader_duplicate_header_throws()
    {
        Should.Throw<FormatException>(() => CsvParser.ParseWithHeader("name,name\nx,y"));
    }
}
