# Copyright 2018-2026 Jérôme Dumonteil
# Copyright (c) 2009-2010 Ars Aperta, Itaapy, Pierlis, Talend.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
#
# Authors (odfdo project): jerome.dumonteil@gmail.com
# The odfdo project is a derivative work of the lpod-python project:
# https://github.com/lpod/lpod-python
from __future__ import annotations

from collections import namedtuple
from collections.abc import Iterable
from datetime import datetime

import pytest

from odfdo.document import Document
from odfdo.header import Header
from odfdo.paragraph import Paragraph
from odfdo.tracked_changes import (
    ChangeInfo,
    TextChange,
    TextChangedRegion,
    TextChangeEnd,
    TextChangeStart,
    TextDeletion,
    TextFormatChange,
    TextInsertion,
    TrackedChanges,
)

Sample = namedtuple("Sample", ["doc", "changes"])


@pytest.fixture
def sample(samples) -> Iterable[Sample]:
    document = Document(samples("tracked_changes.odt"))
    yield Sample(
        doc=document,
        changes=document.body.get_tracked_changes(),
    )


@pytest.fixture
def sample_doc() -> Iterable[Document]:
    doc = Document("odt")
    body = doc.body
    body.clear()
    body.append(Header(1, "Title"))
    body.append(Paragraph("Some text."))
    yield doc


def test_change_info_class():
    ci = ChangeInfo()
    assert isinstance(ci, ChangeInfo)


def test_change_info_class_creator():
    name = "John Doe"
    dt = datetime(1977, 12, 25, 12, 0, 0)
    ci = ChangeInfo(name, dt)
    assert ci.creator == name


def test_change_info_class_date():
    name = "John Doe"
    dt = datetime(1977, 12, 25, 12, 0, 0)
    ci = ChangeInfo(name, dt)
    assert ci.date == dt


def test_change_info_comments_none():
    name = "John Doe"
    dt = datetime(1977, 12, 25, 12, 0, 0)
    ci = ChangeInfo(name, dt)
    assert ci.get_comments() == ""


def test_change_info_comments():
    name = "John Doe"
    dt = datetime(1977, 12, 25, 12, 0, 0)
    comment = "Some comment"
    ci = ChangeInfo(name, dt)
    ci.set_comments(comment)
    assert ci.get_comments() == comment


def test_change_info_comments_replace():
    name = "John Doe"
    dt = datetime(1977, 12, 25, 12, 0, 0)
    comment1 = "Some comment"
    comment2 = "More comment"
    ci = ChangeInfo(name, dt)
    ci.set_comments(comment1)
    ci.set_comments(comment2)
    assert ci.get_comments() == comment2


def test_change_info_comments_no_replace():
    name = "John Doe"
    dt = datetime(1977, 12, 25, 12, 0, 0)
    comment1 = "Some comment"
    comment2 = "More comment"
    ci = ChangeInfo(name, dt)
    ci.set_comments(comment1)
    ci.set_comments(comment2, replace=False)
    expected = f"{comment1}\n{comment2}"
    assert ci.get_comments() == expected


def test_change_info_comments_no_replace_no_join():
    name = "John Doe"
    dt = datetime(1977, 12, 25, 12, 0, 0)
    comment1 = "Some comment"
    comment2 = "More comment"
    ci = ChangeInfo(name, dt)
    ci.set_comments(comment1)
    ci.set_comments(comment2, replace=False)
    expected = [comment1, comment2]
    assert ci.get_comments(joined=False) == expected


def test_text_insertion_class():
    ti = TextInsertion()
    assert isinstance(ti, TextInsertion)


def test_text_insertion_empty_deleted():
    ti = TextInsertion()
    assert ti.get_deleted() is None


def test_text_insertion_empty_deleted_as_text():
    ti = TextInsertion()
    assert ti.get_deleted(as_text=True) == ""


def test_text_insertion_empty_inserted():
    ti = TextInsertion()
    with pytest.raises(TypeError):
        ti.get_inserted()


def test_text_insertion_empty_change_info():
    ti = TextInsertion()
    assert ti.get_change_info() is None


def test_text_insertion_empty_set_change_info():
    ti = TextInsertion()
    name = "John Doe"
    dt = datetime(1977, 12, 25, 12, 0, 0)
    comment = Paragraph("Some comment")
    ti.set_change_info(None, name, dt, comment)
    ci = ti.get_change_info()
    assert isinstance(ci, ChangeInfo)


def test_text_insertion_empty_set_change_info_comment_list():
    ti = TextInsertion()
    name = "John Doe"
    dt = datetime(1977, 12, 25, 12, 0, 0)
    comments = [Paragraph("Some comment"), Paragraph("Other comment")]
    ti.set_change_info(None, name, dt, comments)
    ci = ti.get_change_info()
    assert isinstance(ci, ChangeInfo)


def test_text_insertion_empty_set_change_info_get():
    ti = TextInsertion()
    name = "John Doe"
    dt = datetime(1977, 12, 25, 12, 0, 0)
    comment = Paragraph("Some comment")
    ti.set_change_info(None, name, dt, comment)
    ci = ti.get_change_info()
    assert ci.creator == name
    assert ci.date == dt
    assert ci.get_comments() == str(comment).strip()


def test_text_insertion_empty_set_change_info_get_no_comment():
    ti = TextInsertion()
    name = "John Doe"
    dt = datetime(1977, 12, 25, 12, 0, 0)
    ti.set_change_info(None, name, dt)
    ci = ti.get_change_info()
    assert ci.creator == name
    assert ci.date == dt
    assert ci.get_comments() == ""


def test_text_insertion_empty_set_change_info_get_comment_list():
    ti = TextInsertion()
    name = "John Doe"
    dt = datetime(1977, 12, 25, 12, 0, 0)
    t1 = "Some comment"
    t2 = "Other comment"
    comments = [Paragraph(t1), Paragraph(t2)]
    ti.set_change_info(None, name, dt, comments)
    ci = ti.get_change_info()
    assert ci.creator == name
    assert ci.date == dt
    assert ci.get_comments(joined=False) == [t1, t2]


def test_text_insertion_empty_set_change_info_get_comment_list_wrong():
    ti = TextInsertion()
    name = "John Doe"
    dt = datetime(1977, 12, 25, 12, 0, 0)
    t1 = "Some comment"
    comments = [Paragraph(t1), "wrong"]
    with pytest.raises(TypeError):
        ti.set_change_info(None, name, dt, comments)


def test_text_insertion_get_from_text_changed_region(sample):
    tracked_changes = sample.changes
    cr = tracked_changes.get_changed_region(1)
    assert isinstance(cr, TextChangedRegion)
    ti = cr.get_change_element()
    assert isinstance(ti, TextInsertion)


def test_text_insertion_get_inserted(sample):
    tracked_changes = sample.changes
    cr = tracked_changes.get_changed_region(1)
    ti = cr.get_change_element()
    content = ti.get_inserted()
    assert isinstance(content, list)
    p = content[0]
    assert isinstance(p, Paragraph)
    assert str(p).strip() == "los bonitos amigos"


def test_text_insertion_get_inserted_as_text(sample):
    tracked_changes = sample.changes
    cr = tracked_changes.get_changed_region(1)
    ti = cr.get_change_element()
    content = ti.get_inserted(as_text=True)
    assert content.strip() == "los bonitos amigos"


def test_text_insertion_get_inserted_as_text_no_header(sample):
    tracked_changes = sample.changes
    cr = tracked_changes.get_changed_region(1)
    ti = cr.get_change_element()
    content = ti.get_inserted(as_text=True, no_header=True)
    assert content.strip() == "los bonitos amigos"


def test_text_insertion_set_ci_wrong(sample):
    tracked_changes = sample.changes
    cr = tracked_changes.get_changed_region(1)
    ti = cr.get_change_element()
    with pytest.raises(TypeError):
        ti.set_change_info(change_info="wrong")


def test_text_insertion_set_ci_new(sample):
    tracked_changes = sample.changes
    cr = tracked_changes.get_changed_region(1)
    ti = cr.get_change_element()
    name = "John Doe"
    dt = datetime(1977, 12, 25, 12, 0, 0)
    ci = ChangeInfo(name, dt)
    ti.set_change_info(change_info=ci)
    new_ci = ti.get_change_info()
    assert new_ci.creator == name
    assert new_ci.date == dt


def test_text_deletion_class():
    td = TextDeletion()
    assert isinstance(td, TextDeletion)


def test_text_deletion_empty_deleted():
    td = TextDeletion()
    assert td.get_deleted() == []


def test_text_deletion_empty_deleted_as_text():
    td = TextDeletion()
    assert td.get_deleted(as_text=True) == ""


def test_text_deletion_empty_deleted_no_header():
    td = TextDeletion()
    assert td.get_deleted(no_header=True) == []


def test_text_deletion_empty_inserted():
    td = TextDeletion()
    assert td.get_inserted() is None


def test_text_deletion_empty_inserted_as_text():
    td = TextDeletion()
    assert td.get_inserted(as_text=True) == ""


def test_text_deletion_empty_change_info():
    td = TextDeletion()
    assert td.get_change_info() is None


def test_text_deletion_get_from_text_changed_region(sample):
    tracked_changes = sample.changes
    cr = tracked_changes.get_changed_region(0)
    assert isinstance(cr, TextChangedRegion)
    td = cr.get_change_element()
    assert isinstance(td, TextDeletion)


def test_text_deletion_get_deleted_from_text_changed_region(sample):
    tracked_changes = sample.changes
    cr = tracked_changes.get_changed_region(0)
    td = cr.get_change_element()
    assert len(td.get_deleted()) == 1
    assert str(td.get_deleted()[0]).strip() == "les"


def test_text_deletion_get_deleted_from_text_changed_region_as_text(sample):
    tracked_changes = sample.changes
    cr = tracked_changes.get_changed_region(0)
    td = cr.get_change_element()
    assert td.get_deleted(as_text=True).strip() == "les"


def test_text_deletion_get_deleted_from_text_changed_region_no_header(sample):
    tracked_changes = sample.changes
    cr = tracked_changes.get_changed_region(0)
    td = cr.get_change_element()
    assert str(td.get_deleted(no_header=True)[0]).strip() == "les"


def test_text_deletion_set_deleted(sample_doc):
    body = sample_doc.body
    p = body.get_paragraph(content="Some")
    assert isinstance(p, Paragraph)
    td = TextDeletion()
    assert len(td.get_deleted()) == 0
    td.set_deleted(p)
    assert len(td.get_deleted()) == 1


def test_text_deletion_set_deleted_list(sample_doc):
    body = sample_doc.body
    p = body.get_paragraph(content="Some")
    assert isinstance(p, Paragraph)
    td = TextDeletion()
    assert len(td.get_deleted()) == 0
    td.set_deleted([p])
    assert len(td.get_deleted()) == 1


def test_text_deletion_set_deleted_list_2(sample_doc):
    body = sample_doc.body
    h = body.get_header(content="Title")
    assert isinstance(h, Header)
    td = TextDeletion()
    assert len(td.get_deleted()) == 0
    td.set_deleted([h])
    assert len(td.get_deleted()) == 1


def test_text_deletion_set_deleted_list_3(sample_doc):
    body = sample_doc.body
    h = body.get_header(content="Title")
    assert isinstance(h, Header)
    p = body.get_paragraph(content="Some")
    assert isinstance(p, Paragraph)
    td = TextDeletion()
    assert len(td.get_deleted()) == 0
    td.set_deleted([h, p])
    assert len(td.get_deleted()) == 2


def test_text_deletion_set_deleted_list_2_replace(sample_doc):
    body = sample_doc.body
    h = body.get_header(content="Title")
    assert isinstance(h, Header)
    p = body.get_paragraph(content="Some")
    assert isinstance(p, Paragraph)
    td = TextDeletion()
    assert len(td.get_deleted()) == 0
    td.set_deleted([h])
    assert len(td.get_deleted()) == 1
    td.set_deleted([p])
    assert len(td.get_deleted()) == 1


def test_text_deletion_set_deleted_get_no_header(sample_doc):
    body = sample_doc.body
    h = body.get_header(content="Title")
    assert isinstance(h, Header)
    p = body.get_paragraph(content="Some")
    assert isinstance(p, Paragraph)
    td = TextDeletion()
    assert len(td.get_deleted()) == 0
    td.set_deleted([h, p])
    result = td.get_deleted(no_header=True)
    assert len(result) == 2
    assert isinstance(result[0], Paragraph)
    assert isinstance(result[1], Paragraph)


def test_text_format_change_class():
    tfc = TextFormatChange()
    assert isinstance(tfc, TextFormatChange)


def test_text_changed_region_class():
    tcr = TextChangedRegion()
    assert isinstance(tcr, TextChangedRegion)


def test_text_changed_region_get_changed_region(sample):
    trc = sample.doc.body.get_tracked_changes()
    assert isinstance(trc, TrackedChanges)
    tcr = trc.get_changed_region(0)
    assert isinstance(tcr, TextChangedRegion)


def test_text_changed_region_get_change_info(sample):
    trc = sample.doc.body.get_tracked_changes()
    tcr = trc.get_changed_region(0)
    ci = tcr.get_change_info()
    assert isinstance(ci, ChangeInfo)


def test_text_changed_region_set_change_info(sample):
    trc = sample.doc.body.get_tracked_changes()
    tcr = trc.get_changed_region(0)
    name = "John Doe"
    dt = datetime(1977, 12, 25, 12, 0, 0)
    new_ci = ChangeInfo(name, dt)
    tcr.set_change_info(new_ci)
    result_ci = tcr.get_change_info()
    assert result_ci.creator == name


def test_text_changed_region_set_change_info_wrong(sample):
    tcr = TextChangedRegion()
    name = "John Doe"
    dt = datetime(1977, 12, 25, 12, 0, 0)
    new_ci = ChangeInfo(name, dt)
    with pytest.raises(ValueError):
        tcr.set_change_info(new_ci)


def test_text_changed_region_set_id(sample):
    trc = sample.doc.body.get_tracked_changes()
    tcr = trc.get_changed_region(0)
    tcr.set_id("123")
    assert tcr.get_id() == "123"


def test_text_changed_region_get_xml_id(sample):
    trc = sample.doc.body.get_tracked_changes()
    tcr = trc.get_changed_region(0)
    assert tcr._get_xml_id() == "ct140266191788864"


def test_tracked_changes_class():
    tcs = TrackedChanges()
    assert isinstance(tcs, TrackedChanges)


def test_tracked_changes_per_role_deletion(sample):
    trc = sample.doc.body.get_tracked_changes()
    regions = trc.get_changed_regions(role="deletion")
    assert len(regions) == 2


def test_tracked_changes_per_role_insertion(sample):
    trc = sample.doc.body.get_tracked_changes()
    regions = trc.get_changed_regions(role="insertion")
    assert len(regions) == 1


def test_tracked_changes_per_role_insertion_empty(sample):
    trc = TrackedChanges()
    regions = trc.get_changed_regions(role="insertion")
    assert len(regions) == 0


def test_text_change_class():
    tc = TextChange()
    assert isinstance(tc, TextChange)


def test_text_change_class_base_class_1():
    tc = TextChange()
    assert tc.get_inserted() is None


def test_text_change_class_base_class_2():
    tc = TextChange()
    assert tc.get_start() is None


def test_text_change_class_base_class_3():
    tc = TextChange()
    assert tc.get_end() is None


def test_text_change_class_base_class_4():
    tc = TextChange()
    with pytest.raises(ValueError):
        tc.get_changed_region()


def test_text_change_get(sample):
    body = sample.doc.body
    changes = body.get_text_changes()
    assert len(changes) == 3


def test_text_changes_property(sample):
    body = sample.doc.body
    changes = body.text_changes
    assert len(changes) == 3


def test_get_text_change(sample):
    body = sample.doc.body
    change = body.get_text_change(0)
    assert isinstance(change, TextChange)
    assert change.get_id() == "ct140266191788864"


def test_get_text_change_idx(sample):
    body = sample.doc.body
    change = body.get_text_change(idx="ct140266191788864")
    assert isinstance(change, TextChange)
    assert change.get_id() == "ct140266191788864"


def test_get_text_change_idx_start(sample):
    body = sample.doc.body
    # ct140266193096576 is a change-start in the sample
    change = body.get_text_change(idx="ct140266193096576")
    assert isinstance(change, TextChangeStart)
    assert change.get_id() == "ct140266193096576"


def test_get_text_change_none(sample_doc):
    body = sample_doc.body
    change = body.get_text_change(0)
    assert change is None


def test_get_text_change_idx_none(sample_doc):
    body = sample_doc.body
    change = body.get_text_change(idx="nonexistent")
    assert change is None


def test_text_change_get_id(sample):
    body = sample.doc.body
    change = body.get_text_changes()[0]
    assert change.get_id() == "ct140266191788864"


def test_text_change_set_id(sample):
    body = sample.doc.body
    change = body.get_text_changes()[0]
    change.set_id("aaa123")
    assert change.get_id() == "aaa123"


def test_text_get_changed_region(sample):
    body = sample.doc.body
    change = body.get_text_changes()[0]
    changed_region = change.get_changed_region()
    assert isinstance(changed_region, TextChangedRegion)


def test_text_get_changed_region_2(sample):
    body = sample.doc.body
    change = body.get_text_changes()[0]
    tracked_changes = body.get_tracked_changes()
    changed_region = change.get_changed_region(tracked_changes)
    assert isinstance(changed_region, TextChangedRegion)


def test_text_get_change_info(sample):
    body = sample.doc.body
    change = body.get_text_changes()[0]
    ci = change.get_change_info()
    assert isinstance(ci, ChangeInfo)
    assert ci.creator == "Romain Gauthier"


def test_text_get_change_info_2(sample):
    body = sample.doc.body
    change = body.get_text_changes()[0]
    tracked_changes = body.get_tracked_changes()
    ci = change.get_change_info(tracked_changes)
    assert isinstance(ci, ChangeInfo)
    assert ci.creator == "Romain Gauthier"


def test_text_get_change_element_1(sample):
    body = sample.doc.body
    change = body.get_text_changes()[0]
    # tracked_changes = body.get_tracked_changes()
    ce = change.get_change_element()
    assert isinstance(ce, TextDeletion)


def test_text_get_change_element_2(sample):
    body = sample.doc.body
    change = body.get_text_changes()[0]
    tracked_changes = body.get_tracked_changes()
    ce = change.get_change_element(tracked_changes)
    assert isinstance(ce, TextDeletion)


def test_text_get_change_element_3(sample):
    body = sample.doc.body
    change = body.get_text_changes()[1]
    ce = change.get_change_element()
    assert isinstance(ce, TextInsertion)


def test_text_get_change_element_4(sample):
    body = sample.doc.body
    change = body.get_text_changes()[2]
    ce = change.get_change_element()
    assert isinstance(ce, TextDeletion)


def test_text_get_deleted_1(sample):
    body = sample.doc.body
    change = body.get_text_changes()[0]
    ce = change.get_change_element()
    deleted = ce.get_deleted()
    assert str(deleted[0]).strip() == "les"


def test_text_get_deleted_2(sample):
    body = sample.doc.body
    change = body.get_text_changes()[1]
    ce = change.get_change_element()
    deleted = ce.get_deleted()
    assert deleted is None


def test_text_get_deleted_3(sample):
    body = sample.doc.body
    change = body.get_text_changes()[2]
    ce = change.get_change_element()
    deleted = ce.get_deleted()
    assert str(deleted[0]).strip() == "amis,"


def test_text_change_end_class():
    tce = TextChangeEnd()
    assert isinstance(tce, TextChangeEnd)


def test_text_change_end_none_start():
    tce = TextChangeEnd()
    assert tce.get_start() is None


def test_text_change_end_get_end():
    tce = TextChangeEnd()
    assert tce.get_end() == tce


def test_text_change_end_get_deleted():
    tce = TextChangeEnd()
    assert tce.get_deleted() is None


def test_text_change_end_get_inserted_0():
    tce = TextChangeEnd()
    assert tce.get_inserted() is None


def test_text_change_end_get_inserted_1():
    tce = TextChangeEnd()
    assert tce.get_inserted(as_text=True) == ""


def test_text_change_start_class():
    tcst = TextChangeStart()
    assert isinstance(tcst, TextChangeStart)


def test_text_change_start_none_start():
    tcst = TextChangeStart()
    assert tcst.get_start() == tcst


def test_text_change_start_get_end():
    tcst = TextChangeStart()
    assert tcst.get_end() is None


def test_text_change_start_elem_1(sample):
    body = sample.doc.body
    starts = body.get_text_change_starts()
    assert len(starts) == 1
    assert isinstance(starts[0], TextChangeStart)


def test_text_change_start_elem_2(sample):
    body = sample.doc.body
    start = body.get_text_change_start(0)
    assert isinstance(start, TextChangeStart)


def test_text_change_start_elem_3(sample):
    body = sample.doc.body
    start = body.get_text_change_start(42)
    assert start is None


def test_text_change_end_elem_1(sample):
    body = sample.doc.body
    ends = body.get_text_change_ends()
    assert len(ends) == 1
    assert isinstance(ends[0], TextChangeEnd)


def test_text_change_end_elem_2(sample):
    body = sample.doc.body
    end = body.get_text_change_end(0)
    assert isinstance(end, TextChangeEnd)


def test_text_change_end_elem_3(sample):
    body = sample.doc.body
    end = body.get_text_change_end(42)
    assert end is None


def test_text_change_start_delete(sample):
    body = sample.doc.body
    start = body.get_text_change_start(0)
    start.delete()
    start2 = body.get_text_change_start(0)
    assert start2 is None
    end2 = body.get_text_change_end(0)
    assert end2 is None


def test_text_change_start_get_deleted(sample):
    body = sample.doc.body
    start = body.get_text_change(0)
    assert isinstance(start.get_deleted()[0], Paragraph)


def test_get_text_change_deletions(sample):
    body = sample.doc.body
    deletions = body.get_text_change_deletions()
    assert len(deletions) == 2
    assert isinstance(deletions[0], TextChange)


def test_get_text_change_deletion(sample):
    body = sample.doc.body
    deletion = body.get_text_change_deletion(0)
    assert isinstance(deletion, TextChange)
    assert deletion.get_id() == "ct140266191788864"


def test_get_text_change_deletion_idx(sample):
    body = sample.doc.body
    deletion = body.get_text_change_deletion(idx="ct140266191788864")
    assert isinstance(deletion, TextChange)
    assert deletion.get_id() == "ct140266191788864"


def test_get_text_change_deletions_empty(sample_doc):
    body = sample_doc.body
    deletions = body.get_text_change_deletions()
    assert deletions == []


def test_get_text_change_deletion_none(sample_doc):
    body = sample_doc.body
    deletion = body.get_text_change_deletion(0)
    assert deletion is None


def test_get_text_change_deletion_idx_none(sample_doc):
    body = sample_doc.body
    deletion = body.get_text_change_deletion(idx="nonexistent")
    assert deletion is None
