import csv
import io
import sys

from testutils import assert_raises

for row in csv.reader(["one,two,three"]):
    [one, two, three] = row
    assert one == "one"
    assert two == "two"
    assert three == "three"


def f():
    iter = ["one,two,three", "four,five,six"]
    reader = csv.reader(iter)

    [one, two, three] = next(reader)
    [four, five, six] = next(reader)

    assert one == "one"
    assert two == "two"
    assert three == "three"
    assert four == "four"
    assert five == "five"
    assert six == "six"


f()


def test_delim():
    iter = ["one|two|three", "four|five|six"]
    reader = csv.reader(iter, delimiter="|")

    [one, two, three] = next(reader)
    [four, five, six] = next(reader)

    assert one == "one"
    assert two == "two"
    assert three == "three"
    assert four == "four"
    assert five == "five"
    assert six == "six"

    with assert_raises(TypeError):
        iter = ["one,,two,,three"]
        csv.reader(iter, delimiter=",,")


test_delim()


def test_quote_strings_and_notnull_writer():
    string_buf = io.StringIO()
    csv.writer(string_buf, quoting=csv.QUOTE_STRINGS).writerow(["x", 1, None, ""])
    assert string_buf.getvalue() == '"x",1,,""\r\n'

    notnull_buf = io.StringIO()
    csv.writer(notnull_buf, quoting=csv.QUOTE_NOTNULL).writerow(["x", 1, None, ""])
    assert notnull_buf.getvalue() == '"x","1",,""\r\n'

    for quoting in (csv.QUOTE_STRINGS, csv.QUOTE_NOTNULL):
        buf = io.StringIO()
        csv.writer(buf, quoting=quoting).writerow([None, None])
        assert buf.getvalue() == ",\r\n"

        with assert_raises(csv.Error):
            csv.writer(io.StringIO(), quoting=quoting).writerow([None])

        with assert_raises(TypeError):
            csv.writer(io.StringIO(), quoting=quoting, quotechar=None)


test_quote_strings_and_notnull_writer()


def test_quote_none_writer_without_quotechar():
    no_quotechar_buf = io.StringIO()
    csv.writer(
        no_quotechar_buf,
        quoting=csv.QUOTE_NONE,
        quotechar=None,
        escapechar="\\",
    ).writerow(["a,b", 'x"y'])
    assert no_quotechar_buf.getvalue() == 'a\\,b,x"y\r\n'

    default_quotechar_buf = io.StringIO()
    csv.writer(
        default_quotechar_buf,
        quoting=csv.QUOTE_NONE,
        escapechar="\\",
    ).writerow(["a,b", 'x"y'])
    assert default_quotechar_buf.getvalue() == 'a\\,b,x\\"y\r\n'

    escapechar_buf = io.StringIO()
    csv.writer(
        escapechar_buf,
        quoting=csv.QUOTE_NONE,
        quotechar=None,
        escapechar="\\",
    ).writerow(["a\\b"])
    assert escapechar_buf.getvalue() == "a\\\\b\r\n"

    linebreak_buf = io.StringIO()
    csv.writer(
        linebreak_buf,
        quoting=csv.QUOTE_NONE,
        quotechar=None,
        escapechar="\\",
    ).writerow(["a\rb", "c\nd"])
    assert linebreak_buf.getvalue() == "a\\\rb,c\\\nd\r\n"

    with assert_raises(csv.Error):
        csv.writer(io.StringIO(), quoting=csv.QUOTE_NONE, quotechar=None).writerow(
            ["a,b"]
        )

    with assert_raises(csv.Error):
        csv.writer(
            io.StringIO(),
            quoting=csv.QUOTE_NONE,
            quotechar=None,
            escapechar="\\",
        ).writerow([None])

    two_empty_buf = io.StringIO()
    csv.writer(
        two_empty_buf,
        quoting=csv.QUOTE_NONE,
        quotechar=None,
        escapechar="\\",
    ).writerow([None, ""])
    assert two_empty_buf.getvalue() == ",\r\n"


test_quote_none_writer_without_quotechar()


def test_quote_none_reader_skipinitialspace_escapechar():
    reader = csv.reader(
        ["a,  b,\\ c,d"],
        quoting=csv.QUOTE_NONE,
        escapechar="\\",
        skipinitialspace=True,
    )
    assert list(reader) == [["a", "b", " c", "d"]]


test_quote_none_reader_skipinitialspace_escapechar()


def test_quote_minimal_writer_lineterminator():
    # https://github.com/RustPython/RustPython/issues/8302
    # QUOTE_MINIMAL must quote '\r' and '\n' regardless of the line terminator.
    buf = io.StringIO()
    writer = csv.writer(buf, lineterminator="!")
    writer.writerow(["a", "b"])
    writer.writerow([1, 2])
    writer.writerow(["\r", "\n"])
    assert buf.getvalue() == 'a,b!1,2!"\r","\n"!'

    nul = io.StringIO()
    csv.writer(nul, lineterminator="\0").writerow(["\r", "\n"])
    assert nul.getvalue() == '"\r","\n"\0'

    crlf = io.StringIO()
    csv.writer(crlf, lineterminator="!").writerow(["\r\n"])
    assert crlf.getvalue() == '"\r\n"!'

    # the terminator character itself still triggers quoting
    term = io.StringIO()
    csv.writer(term, lineterminator="!").writerow(["a!b", "c"])
    assert term.getvalue() == '"a!b",c!'

    # default terminator behavior is unchanged
    default = io.StringIO()
    csv.writer(default).writerow(["\r", "\n"])
    assert default.getvalue() == '"\r","\n"\r\n'


test_quote_minimal_writer_lineterminator()


def test_multichar_lineterminator():
    # https://github.com/RustPython/RustPython/issues/8322
    # The writer must store and emit a full multi-character line terminator.
    for lineterminator in "\r\n", "\n", "\r", "!@#", "\0":
        buf = io.StringIO()
        writer = csv.writer(buf, lineterminator=lineterminator)
        writer.writerow(["a", "b"])
        writer.writerow([1, 2])
        writer.writerow(["\r", "\n"])
        assert buf.getvalue() == (
            f'a,b{lineterminator}1,2{lineterminator}"\r","\n"{lineterminator}'
        ), (lineterminator, buf.getvalue())

    # A field is quoted when it contains any byte of the terminator (QUOTE_MINIMAL).
    for field, expected in [
        ("a@b", '"a@b",x!@#'),
        ("a!b", '"a!b",x!@#'),
        ("a#b", '"a#b",x!@#'),
        ("abc", "abc,x!@#"),
    ]:
        buf = io.StringIO()
        csv.writer(buf, lineterminator="!@#").writerow([field, "x"])
        assert buf.getvalue() == expected, (field, buf.getvalue())

    # The csv-core-backed QUOTE_ALL / QUOTE_NONNUMERIC paths emit the full
    # terminator too, and keep the state machine correct across rows.
    allq = io.StringIO()
    writer = csv.writer(allq, lineterminator="!@#", quoting=csv.QUOTE_ALL)
    writer.writerow(["a", "b"])
    writer.writerow(["c", "d"])
    assert allq.getvalue() == '"a","b"!@#"c","d"!@#', allq.getvalue()

    nonnum = io.StringIO()
    csv.writer(nonnum, lineterminator="!@#", quoting=csv.QUOTE_NONNUMERIC).writerow(
        ["a", 1]
    )
    assert nonnum.getvalue() == '"a",1!@#', nonnum.getvalue()

    # A field that itself contains a line-break byte must be kept intact: the
    # csv-core path drops only the trailing record terminator, not a byte from
    # the field data.
    embedded = io.StringIO()
    csv.writer(embedded, lineterminator="!@#", quoting=csv.QUOTE_ALL).writerow(
        ["x\ny", "z"]
    )
    assert embedded.getvalue() == '"x\ny","z"!@#', embedded.getvalue()

    # QUOTE_NONE escapes any byte of the terminator.
    none = io.StringIO()
    csv.writer(
        none, lineterminator="!@#", quoting=csv.QUOTE_NONE, escapechar="\\"
    ).writerow(["a!b", "x"])
    assert none.getvalue() == "a\\!b,x!@#", none.getvalue()

    # register_dialect round-trips a multi-character terminator.
    csv.register_dialect("multichar_lt", delimiter=",", lineterminator="!@#")
    try:
        reg = io.StringIO()
        csv.writer(reg, dialect="multichar_lt").writerow(["a", "b"])
        assert reg.getvalue() == "a,b!@#", reg.getvalue()
    finally:
        csv.unregister_dialect("multichar_lt")

    # The dialect attribute reflects the full terminator.
    assert (
        csv.writer(io.StringIO(), lineterminator="!@#").dialect.lineterminator == "!@#"
    )

    # The reader ignores lineterminator (like CPython) and only splits on \r\n.
    assert list(csv.reader(io.StringIO("a,b!@#c,d!@#"), lineterminator="!@#")) == [
        ["a", "b!@#c", "d!@#"]
    ]
    assert list(csv.reader(io.StringIO("a,b\r\nc,d\r\n"), lineterminator="!@#")) == [
        ["a", "b"],
        ["c", "d"],
    ]


test_multichar_lineterminator()


def test_reject_non_ascii_lineterminator():
    # CPython accepts non-ASCII line terminators; RustPython rejects them
    # because the writer quotes and escapes byte by byte. Supporting them
    # requires code-point-wise handling as part of full Unicode dialect support.
    with assert_raises(csv.Error):
        csv.writer(io.StringIO(), lineterminator="é")

    with assert_raises(csv.Error):
        csv.writer(io.StringIO(), lineterminator="\x85")

    with assert_raises(csv.Error):
        csv.writer(io.StringIO(), lineterminator="\ud800")

    with assert_raises(csv.Error):
        csv.writer(
            io.StringIO(), lineterminator="é", quoting=csv.QUOTE_NONE, escapechar="\\"
        )

    with assert_raises(csv.Error):
        csv.register_dialect("non_ascii_lt", lineterminator="é")

    class NonAsciiDialect(csv.excel):
        lineterminator = "é"

    with assert_raises(csv.Error):
        NonAsciiDialect()

    buf = io.StringIO()
    csv.writer(buf, lineterminator="!@#").writerow(["a", "b"])
    assert buf.getvalue() == "a,b!@#"


if sys.implementation.name == "rustpython":
    test_reject_non_ascii_lineterminator()


def test_empty_lineterminator():
    class EmptyLineTerminator(csv.excel):
        lineterminator = ""

    keyword = io.StringIO()
    csv.writer(keyword, lineterminator="").writerows([["a", "b"], ["c", "d"]])
    assert keyword.getvalue() == "a,bc,d"

    dialect = io.StringIO()
    csv.writer(dialect, dialect=EmptyLineTerminator).writerows([["a", "b"], ["c", "d"]])
    assert dialect.getvalue() == "a,bc,d"

    source = "a,b\r\nc,d\n"
    assert list(csv.reader(io.StringIO(source), lineterminator="")) == [
        ["a", "b"],
        ["c", "d"],
    ]
    assert list(csv.reader(io.StringIO(source), dialect=EmptyLineTerminator)) == [
        ["a", "b"],
        ["c", "d"],
    ]


test_empty_lineterminator()


def test_quote_minimal_writer_empty_fields():
    buf = io.StringIO()
    writer = csv.writer(buf)
    writer.writerow([""])
    writer.writerow([None])
    writer.writerow([])
    writer.writerow(["", ""])
    assert buf.getvalue() == '""\r\n""\r\n\r\n,\r\n'


test_quote_minimal_writer_empty_fields()


def test_reader_skipinitialspace_preserves_quoted_spaces():
    reader = csv.reader(['a, "b, c", d'], skipinitialspace=True)
    assert list(reader) == [["a", "b, c", "d"]]


test_reader_skipinitialspace_preserves_quoted_spaces()
