using ZB.MOM.WW.ScadaBridge.Commons.Types;

namespace ZB.MOM.WW.ScadaBridge.Commons.Tests;

public class OverrideCsvParserTests
{
    [Fact]
    public void Parse_SimpleThreeColumnFile_ReturnsTwoRowsNoErrors()
    {
        const string csv = "AttributeName,Value,ElementType\nSetpoint,42,Int32\nName,Pump A,\n";

        var result = OverrideCsvParser.Parse(csv);

        Assert.Empty(result.Errors);
        Assert.Equal(2, result.Rows.Count);

        Assert.Equal("Setpoint", result.Rows[0].AttributeName);
        Assert.Equal("42", result.Rows[0].Value);
        Assert.Equal("Int32", result.Rows[0].ElementType);
        Assert.Equal(2, result.Rows[0].LineNumber);

        Assert.Equal("Name", result.Rows[1].AttributeName);
        Assert.Equal("Pump A", result.Rows[1].Value);
        Assert.Null(result.Rows[1].ElementType);
        Assert.Equal(3, result.Rows[1].LineNumber);
    }

    [Fact]
    public void Parse_TwoColumnFileWithoutElementType_RowsHaveNullElementType()
    {
        const string csv = "AttributeName,Value\nSetpoint,42\nName,Pump A\n";

        var result = OverrideCsvParser.Parse(csv);

        Assert.Empty(result.Errors);
        Assert.Equal(2, result.Rows.Count);
        Assert.All(result.Rows, r => Assert.Null(r.ElementType));
        Assert.Equal("42", result.Rows[0].Value);
        Assert.Equal("Pump A", result.Rows[1].Value);
    }

    [Fact]
    public void Parse_QuotedValueWithComma_PreservesEmbeddedComma()
    {
        const string csv = "AttributeName,Value,ElementType\nName,\"a,b,c\",\n";

        var result = OverrideCsvParser.Parse(csv);

        Assert.Empty(result.Errors);
        var row = Assert.Single(result.Rows);
        Assert.Equal("Name", row.AttributeName);
        Assert.Equal("a,b,c", row.Value);
        Assert.Null(row.ElementType);
    }

    [Fact]
    public void Parse_DoubledQuoteEscape_UnescapesToSingleQuote()
    {
        const string csv = "AttributeName,Value,ElementType\nName,\"he said \"\"hi\"\"\",\n";

        var result = OverrideCsvParser.Parse(csv);

        Assert.Empty(result.Errors);
        var row = Assert.Single(result.Rows);
        Assert.Equal("he said \"hi\"", row.Value);
    }

    [Fact]
    public void Parse_EmptyValueField_YieldsNullValue()
    {
        const string csv = "AttributeName,Value,ElementType\nSetpoint,,\n";

        var result = OverrideCsvParser.Parse(csv);

        Assert.Empty(result.Errors);
        var row = Assert.Single(result.Rows);
        Assert.Equal("Setpoint", row.AttributeName);
        Assert.Null(row.Value);
        Assert.Null(row.ElementType);
    }

    [Fact]
    public void Parse_RowWithTooFewColumns_ProducesLineNumberedErrorAndExcludesRow()
    {
        const string csv = "AttributeName,Value,ElementType\nSetpoint\nName,Pump A,\n";

        var result = OverrideCsvParser.Parse(csv);

        // Bad row on line 2 excluded; good row on line 3 retained.
        var row = Assert.Single(result.Rows);
        Assert.Equal("Name", row.AttributeName);
        Assert.Equal(3, row.LineNumber);

        var error = Assert.Single(result.Errors);
        Assert.Contains("2", error);
    }

    [Fact]
    public void Parse_BlankAttributeName_ProducesLineNumberedError()
    {
        const string csv = "AttributeName,Value,ElementType\n,42,Int32\n";

        var result = OverrideCsvParser.Parse(csv);

        Assert.Empty(result.Rows);
        var error = Assert.Single(result.Errors);
        Assert.Contains("2", error);
    }

    [Fact]
    public void Parse_BlankLines_AreSkippedWithoutError()
    {
        const string csv = "AttributeName,Value,ElementType\n\nSetpoint,42,Int32\n   \nName,Pump A,\n";

        var result = OverrideCsvParser.Parse(csv);

        Assert.Empty(result.Errors);
        Assert.Equal(2, result.Rows.Count);
        // LineNumber reflects the true source line (blank line 2 skipped).
        Assert.Equal(3, result.Rows[0].LineNumber);
        Assert.Equal(5, result.Rows[1].LineNumber);
    }

    [Fact]
    public void Parse_MissingHeader_ReturnsZeroRowsAndHeaderError()
    {
        const string csv = "Setpoint,42,Int32\nName,Pump A,\n";

        var result = OverrideCsvParser.Parse(csv);

        Assert.Empty(result.Rows);
        var error = Assert.Single(result.Errors);
        Assert.Contains("header", error, StringComparison.OrdinalIgnoreCase);
    }

    [Fact]
    public void Parse_HeaderIsCaseInsensitiveAndTrimsWhitespace()
    {
        const string csv = "attributename, value , elementtype\nSetpoint,42,Int32\n";

        var result = OverrideCsvParser.Parse(csv);

        Assert.Empty(result.Errors);
        var row = Assert.Single(result.Rows);
        Assert.Equal("Setpoint", row.AttributeName);
        Assert.Equal("42", row.Value);
        Assert.Equal("Int32", row.ElementType);
    }

    [Fact]
    public void Parse_EmptyInput_ReturnsHeaderError()
    {
        var result = OverrideCsvParser.Parse(string.Empty);

        Assert.Empty(result.Rows);
        Assert.Single(result.Errors);
    }

    [Fact]
    public void Parse_UnquotedWhitespace_IsTrimmedButQuotedWhitespacePreserved()
    {
        const string csv = "AttributeName,Value,ElementType\n  Setpoint ,  42  ,Int32\nName,\"  spaced  \",\n";

        var result = OverrideCsvParser.Parse(csv);

        Assert.Empty(result.Errors);
        Assert.Equal(2, result.Rows.Count);
        Assert.Equal("Setpoint", result.Rows[0].AttributeName);
        Assert.Equal("42", result.Rows[0].Value);
        Assert.Equal("  spaced  ", result.Rows[1].Value);
    }

    [Fact]
    public void Parse_MidFieldUnquotedQuote_IsPreservedAsLiteral()
    {
        // A '"' that does NOT open a field (it follows non-whitespace content in an
        // unquoted field) is a literal character — 'va"lue' must survive intact.
        const string csv = "AttributeName,Value,ElementType\nName,va\"lue,Type\n";

        var result = OverrideCsvParser.Parse(csv);

        Assert.Empty(result.Errors);
        var row = Assert.Single(result.Rows);
        Assert.Equal("Name", row.AttributeName);
        Assert.Equal("va\"lue", row.Value);
        Assert.Equal("Type", row.ElementType);
    }

    [Fact]
    public void Parse_UnterminatedQuotedField_ProducesLineNumberedErrorAndExcludesRow()
    {
        // A quote opens the field but is never closed before end-of-line → malformed.
        const string csv = "AttributeName,Value\nName,\"unclosed\n";

        var result = OverrideCsvParser.Parse(csv);

        Assert.Empty(result.Rows);
        var error = Assert.Single(result.Errors);
        Assert.Contains("2", error);
        Assert.Contains("Unterminated", error, StringComparison.OrdinalIgnoreCase);
    }

    [Fact]
    public void Parse_WellFormedQuotedFieldWithComma_StillParses()
    {
        // Regression guard alongside the unterminated-quote fix: a properly closed
        // quoted field embedding a comma must still round-trip.
        const string csv = "AttributeName,Value,ElementType\nName,\"a,b\",Type\n";

        var result = OverrideCsvParser.Parse(csv);

        Assert.Empty(result.Errors);
        var row = Assert.Single(result.Rows);
        Assert.Equal("Name", row.AttributeName);
        Assert.Equal("a,b", row.Value);
        Assert.Equal("Type", row.ElementType);
    }

    [Fact]
    public void Parse_EmptyQuotedField_YieldsNullValue()
    {
        // A bare "" is an empty quoted field; empty → null per the empty-field rule.
        const string csv = "AttributeName,Value,ElementType\nName,\"\",Type\n";

        var result = OverrideCsvParser.Parse(csv);

        Assert.Empty(result.Errors);
        var row = Assert.Single(result.Rows);
        Assert.Equal("Name", row.AttributeName);
        Assert.Null(row.Value);
        Assert.Equal("Type", row.ElementType);
    }

    [Fact]
    public void Parse_QuotedFieldWithTrailingWhitespaceAfterClose_IsAccepted()
    {
        // The closing quote may be followed by ignorable trailing whitespace before
        // the delimiter; the field value itself is preserved verbatim.
        const string csv = "AttributeName,Value,ElementType\nName,\"a,b\"  ,Type\n";

        var result = OverrideCsvParser.Parse(csv);

        Assert.Empty(result.Errors);
        var row = Assert.Single(result.Rows);
        Assert.Equal("a,b", row.Value);
        Assert.Equal("Type", row.ElementType);
    }
}
