import io
import logging
from pathlib import Path

from pymultirole_plugins.v1.schema import Document, DocumentList
from starlette.datastructures import Headers, UploadFile

from pyconverters_pyexcel.pyexcel import PyCSVConverter, PyCSVParameters, PyExcelConverter, PyExcelParameters

CONTENT_TYPE = Headers({"content-type": "application/octet-stream"})


def make_upload(content: bytes, filename: str) -> UploadFile:
    return UploadFile(file=io.BytesIO(content), filename=filename, headers=CONTENT_TYPE)


def test_pyexcel():
    converter = PyExcelConverter()
    parameters = PyExcelParameters(text_cols="Verbatim")
    testdir = Path(__file__).parent
    source = Path(testdir, "data/Echantion_dataverse.xlsx")
    with source.open("rb") as fin:
        docs: list[Document] = converter.convert(
            UploadFile(file=fin, filename=source.name, headers=CONTENT_TYPE), parameters
        )
        assert len(docs) == 1
        assert len(docs[0].sentences) == 871
        assert docs[0].identifier
        assert docs[0].text
    json_file = source.with_suffix(".json")
    dl = DocumentList(root=docs)
    with json_file.open("w") as fout:
        print(dl.model_dump_json(exclude_none=True, exclude_unset=True, indent=2), file=fout)


def test_pyexcel_dtv():
    converter = PyCSVConverter()
    parameters = PyCSVParameters(text_cols="[Verbatim]")
    testdir = Path(__file__).parent
    source = Path(testdir, "data/Feedback_client.dtv")
    with source.open("rb") as fin:
        docs: list[Document] = converter.convert(
            UploadFile(file=fin, filename=source.name, headers=CONTENT_TYPE), parameters
        )
        assert len(docs) == 1
        assert len(docs[0].sentences) == 1624
        assert docs[0].identifier
        assert docs[0].text
    json_file = source.with_suffix(".json")
    dl = DocumentList(root=docs)
    with json_file.open("w") as fout:
        print(dl.model_dump_json(exclude_none=True, exclude_unset=True, indent=2), file=fout)

    source = Path(testdir, "data/Feedback_client2.dtv")
    parameters.separator = "|"
    with source.open("rb") as fin:
        docs: list[Document] = converter.convert(
            UploadFile(file=fin, filename=source.name, headers=CONTENT_TYPE), parameters
        )
        assert len(docs) == 1
        assert len(docs[0].sentences) == 1630
        assert docs[0].identifier
        assert docs[0].text
    json_file = source.with_suffix(".json")
    dl = DocumentList(root=docs)
    with json_file.open("w") as fout:
        print(dl.model_dump_json(exclude_none=True, exclude_unset=True, indent=2), file=fout)

    source = Path(testdir, "data/Echantion_dataverse 29072025.csv")
    with source.open("rb") as fin:
        docs: list[Document] = converter.convert(
            UploadFile(file=fin, filename=source.name, headers=CONTENT_TYPE), parameters
        )
        assert len(docs) == 1
        assert docs[0].identifier
        assert len(docs[0].sentences) == 871
        assert docs[0].text
    json_file = source.with_suffix(".json")
    dl = DocumentList(root=docs)
    with json_file.open("w") as fout:
        print(dl.model_dump_json(exclude_none=True, exclude_unset=True, indent=2), file=fout)


def test_get_model():
    assert PyExcelConverter.get_model() is PyExcelParameters
    assert PyCSVConverter.get_model() is PyCSVParameters


def test_pycsv_no_text_cols():
    """When text_cols is None all columns become metadata and sentence texts are empty."""
    converter = PyCSVConverter()
    parameters = PyCSVParameters()  # text_cols defaults to None
    content = b"col1,col2\nhello,world\nfoo,bar\n"
    docs = converter.convert(make_upload(content, "test.csv"), parameters)
    assert len(docs) == 1
    doc = docs[0]
    assert len(doc.sentences) == 2
    assert doc.sentences[0].metadata == {"col1": "hello", "col2": "world"}
    assert doc.sentences[1].metadata == {"col1": "foo", "col2": "bar"}
    # sentences have no text (start == end)
    assert doc.sentences[0].start == doc.sentences[0].end
    assert doc.sentences[1].start == doc.sentences[1].end


def test_pycsv_multiple_text_cols():
    """Multiple text columns are joined with a newline inside each sentence."""
    converter = PyCSVConverter()
    parameters = PyCSVParameters(text_cols="col1,col2", metadata_cols="col3")
    content = b"col1,col2,col3\nhello,world,meta1\nfoo,bar,meta2\n"
    docs = converter.convert(make_upload(content, "test.csv"), parameters)
    assert len(docs) == 1
    doc = docs[0]
    assert len(doc.sentences) == 2
    s0, s1 = doc.sentences
    assert doc.text[s0.start : s0.end] == "hello\nworld"
    assert s0.metadata == {"col3": "meta1"}
    assert doc.text[s1.start : s1.end] == "foo\nbar"
    assert s1.metadata == {"col3": "meta2"}


def test_pycsv_explicit_metadata_cols():
    """Only the explicitly listed metadata columns appear in sentence metadata."""
    converter = PyCSVConverter()
    parameters = PyCSVParameters(text_cols="col1", metadata_cols="col2")
    content = b"col1,col2,col3\nhello,world,other\n"
    docs = converter.convert(make_upload(content, "test.csv"), parameters)
    assert len(docs) == 1
    doc = docs[0]
    assert doc.sentences[0].metadata == {"col2": "world"}
    assert "col3" not in doc.sentences[0].metadata


def test_pycsv_unknown_text_col(caplog):
    """An unknown column name is skipped with a warning; known columns still work."""
    converter = PyCSVConverter()
    parameters = PyCSVParameters(text_cols="nonexistent,col1", metadata_cols="col2")
    content = b"col1,col2\nhello,world\n"
    with caplog.at_level(logging.WARNING, logger="pymultirole"):
        docs = converter.convert(make_upload(content, "test.csv"), parameters)
    assert len(docs) == 1
    doc = docs[0]
    # "nonexistent" was skipped; only col1 contributes to text
    assert doc.text[doc.sentences[0].start : doc.sentences[0].end] == "hello"
    assert "nonexistent" in caplog.text


def test_pyexcel_invalid_file():
    """A corrupt XLSX input is caught and [None] is returned."""
    converter = PyExcelConverter()
    parameters = PyExcelParameters(text_cols="col1")
    docs = converter.convert(make_upload(b"not an excel file", "bad.xlsx"), parameters)
    assert docs == [None]


def test_pycsv_invalid_file():
    """A file with bytes invalid for the declared encoding returns [None]."""
    converter = PyCSVConverter()
    # \xff is invalid in UTF-8
    parameters = PyCSVParameters(text_cols="col1")
    docs = converter.convert(make_upload(b"col1\n\xff\n", "bad.csv"), parameters)
    assert docs == [None]
