"""Test suite for opc.pkgwriter module."""

import pytest

from docx.opc.constants import CONTENT_TYPE as CT
from docx.opc.packuri import PackURI
from docx.opc.part import Part
from docx.opc.phys_pkg import _ZipPkgWriter
from docx.opc.pkgwriter import PackageWriter, _ContentTypesItem

from ..unitutil.mock import (
    MagicMock,
    Mock,
    call,
    class_mock,
    instance_mock,
    method_mock,
    patch,
)
from .unitdata.types import a_Default, a_Types, an_Override


class DescribePackageWriter:
    def it_can_write_a_package(self, PhysPkgWriter_, _write_methods):
        # mockery ----------------------
        pkg_file = Mock(name="pkg_file")
        pkg_rels = Mock(name="pkg_rels")
        parts = Mock(name="parts")
        phys_writer = PhysPkgWriter_.return_value
        # exercise ---------------------
        PackageWriter.write(pkg_file, pkg_rels, parts)
        # verify -----------------------
        expected_calls = [
            call._write_content_types_stream(phys_writer, parts),
            call._write_pkg_rels(phys_writer, pkg_rels),
            call._write_parts(phys_writer, parts),
        ]
        PhysPkgWriter_.assert_called_once_with(pkg_file)
        assert _write_methods.mock_calls == expected_calls
        phys_writer.close.assert_called_once_with()

    def it_can_write_a_content_types_stream(self, write_cti_fixture):
        _ContentTypesItem_, parts_, phys_pkg_writer_, blob_ = write_cti_fixture
        PackageWriter._write_content_types_stream(phys_pkg_writer_, parts_)
        _ContentTypesItem_.from_parts.assert_called_once_with(parts_)
        phys_pkg_writer_.write.assert_called_once_with("/[Content_Types].xml", blob_)

    def it_can_write_a_pkg_rels_item(self):
        # mockery ----------------------
        phys_writer = Mock(name="phys_writer")
        pkg_rels = Mock(name="pkg_rels")
        # exercise ---------------------
        PackageWriter._write_pkg_rels(phys_writer, pkg_rels)
        # verify -----------------------
        phys_writer.write.assert_called_once_with("/_rels/.rels", pkg_rels.xml)

    def it_can_write_a_list_of_parts(self):
        # mockery ----------------------
        phys_writer = Mock(name="phys_writer")
        rels = MagicMock(name="rels")
        rels.__len__.return_value = 1
        part1 = Mock(name="part1", _rels=rels)
        part2 = Mock(name="part2", _rels=[])
        # exercise ---------------------
        PackageWriter._write_parts(phys_writer, [part1, part2])
        # verify -----------------------
        expected_calls = [
            call(part1.partname, part1.blob),
            call(part1.partname.rels_uri, part1._rels.xml),
            call(part2.partname, part2.blob),
        ]
        assert phys_writer.write.mock_calls == expected_calls

    # fixtures ---------------------------------------------

    @pytest.fixture
    def blob_(self, request):
        return instance_mock(request, str)

    @pytest.fixture
    def cti_(self, request, blob_):
        return instance_mock(request, _ContentTypesItem, blob=blob_)

    @pytest.fixture
    def _ContentTypesItem_(self, request, cti_):
        _ContentTypesItem_ = class_mock(request, "docx.opc.pkgwriter._ContentTypesItem")
        _ContentTypesItem_.from_parts.return_value = cti_
        return _ContentTypesItem_

    @pytest.fixture
    def parts_(self, request):
        return instance_mock(request, list)

    @pytest.fixture
    def PhysPkgWriter_(self):
        p = patch("docx.opc.pkgwriter.PhysPkgWriter")
        yield p.start()
        p.stop()

    @pytest.fixture
    def phys_pkg_writer_(self, request):
        return instance_mock(request, _ZipPkgWriter)

    @pytest.fixture
    def write_cti_fixture(self, _ContentTypesItem_, parts_, phys_pkg_writer_, blob_):
        return _ContentTypesItem_, parts_, phys_pkg_writer_, blob_

    @pytest.fixture
    def _write_methods(self):
        """Mock that patches all the _write_* methods of PackageWriter"""
        root_mock = Mock(name="PackageWriter")
        patch1 = patch.object(PackageWriter, "_write_content_types_stream")
        patch2 = patch.object(PackageWriter, "_write_pkg_rels")
        patch3 = patch.object(PackageWriter, "_write_parts")
        root_mock.attach_mock(patch1.start(), "_write_content_types_stream")
        root_mock.attach_mock(patch2.start(), "_write_pkg_rels")
        root_mock.attach_mock(patch3.start(), "_write_parts")

        yield root_mock

        patch1.stop()
        patch2.stop()
        patch3.stop()

    @pytest.fixture
    def xml_for_(self, request):
        return method_mock(request, _ContentTypesItem, "xml_for")


class Describe_ContentTypesItem:
    def it_can_compose_content_types_element(self, xml_for_fixture):
        cti, expected_xml = xml_for_fixture
        types_elm = cti._element
        assert types_elm.xml == expected_xml

    # fixtures ---------------------------------------------

    def _mock_part(self, request, name, partname_str, content_type):
        partname = PackURI(partname_str)
        return instance_mock(
            request, Part, name=name, partname=partname, content_type=content_type
        )

    @pytest.fixture(
        params=[
            ("Default", "/ppt/MEDIA/image.PNG", CT.PNG),
            ("Default", "/ppt/media/image.xml", CT.XML),
            ("Default", "/ppt/media/image.rels", CT.OPC_RELATIONSHIPS),
            ("Default", "/ppt/media/image.jpeg", CT.JPEG),
            ("Override", "/docProps/core.xml", "app/vnd.core"),
            ("Override", "/ppt/slides/slide1.xml", "app/vnd.ct_sld"),
            ("Override", "/zebra/foo.bar", "app/vnd.foobar"),
        ]
    )
    def xml_for_fixture(self, request):
        elm_type, partname_str, content_type = request.param
        part_ = self._mock_part(request, "part_", partname_str, content_type)
        cti = _ContentTypesItem.from_parts([part_])
        # expected_xml -----------------
        types_bldr = a_Types().with_nsdecls()
        ext = partname_str.split(".")[-1].lower()
        if elm_type == "Default" and ext not in ("rels", "xml"):
            default_bldr = a_Default()
            default_bldr.with_Extension(ext)
            default_bldr.with_ContentType(content_type)
            types_bldr.with_child(default_bldr)

        types_bldr.with_child(
            a_Default().with_Extension("rels").with_ContentType(CT.OPC_RELATIONSHIPS)
        )
        types_bldr.with_child(
            a_Default().with_Extension("xml").with_ContentType(CT.XML)
        )

        if elm_type == "Override":
            override_bldr = an_Override()
            override_bldr.with_PartName(partname_str)
            override_bldr.with_ContentType(content_type)
            types_bldr.with_child(override_bldr)

        expected_xml = types_bldr.xml()
        return cti, expected_xml
