# coding: utf-8

# Copyright 2014-2025 Álvaro Justen <https://github.com/turicas/rows/>
#    This program is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General
#    Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option)
#    any later version.
#    This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
#    warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
#    more details.
#    You should have received a copy of the GNU Lesser General Public License along with this program.  If not, see
#    <http://www.gnu.org/licenses/>.

from __future__ import unicode_literals

import datetime
import tempfile
import time
import unittest
from collections import OrderedDict

import mock
import pytest

import rows
import tests.utils as utils
from rows.utils import Source

ALIAS_IMPORT, ALIAS_EXPORT = rows.import_from_xls, rows.export_to_xls  # Lazy functions (just aliases)


def date_to_datetime(value):
    return datetime.datetime.fromtimestamp(time.mktime(value.timetuple()))


class PluginXlsTestCase(utils.RowsTestMixIn, unittest.TestCase):

    plugin_name = "xls"
    file_extension = "xls"
    filename = "tests/data/all-field-types.xls"
    expected_meta = {
        "imported_from": "xls",
        "name": "Sheet1",
        "source": Source(uri=filename, plugin_name=plugin_name, encoding=None),
    }

    def test_imports(self):
        # Force the plugin to load
        original_import, original_export = rows.plugins.xls.import_from_xls, rows.plugins.xls.export_to_xls
        assert id(ALIAS_IMPORT) != id(original_import)
        assert id(ALIAS_EXPORT) != id(original_export)
        new_alias_import, new_alias_export = rows.import_from_xls, rows.export_to_xls
        assert id(new_alias_import) == id(original_import)  # Function replaced with loaded one
        assert id(new_alias_export) == id(original_export)  # Function replaced with loaded one

    @mock.patch("rows.plugins.utils.create_table")
    def test_import_from_xls_uses_create_table(self, mocked_create_table):
        mocked_create_table.return_value = 42
        kwargs = {"some_key": 123, "other": 456}
        result = rows.import_from_xls(self.filename, **kwargs)
        assert mocked_create_table.called
        assert mocked_create_table.call_count == 1
        assert result == 42

    @mock.patch("rows.plugins.utils.create_table")
    def test_import_from_xls_retrieve_desired_data(self, mocked_create_table):
        mocked_create_table.return_value = 42

        # import using filename
        rows.import_from_xls(self.filename)
        call_args = mocked_create_table.call_args_list[0]
        self.assert_create_table_data(call_args, expected_meta=self.expected_meta)

        # import using fobj
        with open(self.filename, "rb") as fobj:
            rows.import_from_xls(fobj)
            call_args = mocked_create_table.call_args_list[1]
            self.assert_create_table_data(call_args, expected_meta=self.expected_meta)

    def test_export_to_xls_filename(self):
        # TODO: may test file contents
        temp = tempfile.NamedTemporaryFile(delete=False)
        self.files_to_delete.append(temp.name)
        rows.export_to_xls(utils.table, temp.name)

        table = rows.import_from_xls(temp.name)
        self.assert_table_equal(table, utils.table)

        temp.file.seek(0)
        result = temp.file.read()
        export_in_memory = rows.export_to_xls(utils.table, None)
        assert result == export_in_memory

    def test_export_to_xls_fobj_binary(self):
        temp = tempfile.NamedTemporaryFile(delete=False, mode="wb")
        self.files_to_delete.append(temp.name)
        fobj = temp.file
        result = rows.export_to_xls(utils.table, fobj)
        assert result is fobj
        assert not fobj.closed
        fobj.close()
        # TODO: test file contents instead of this side-effect
        table = rows.import_from_xls(temp.name)
        self.assert_table_equal(table, utils.table)

    def test_export_to_xls_fobj_text(self):
        temp = tempfile.NamedTemporaryFile(delete=False, mode="w")
        self.files_to_delete.append(temp.name)
        fobj = temp.file
        with pytest.raises(ValueError, match="export_to_xls must receive a file-object open in binary mode"):
            rows.export_to_xls(utils.table, fobj)

    @mock.patch("rows.plugins.utils.prepare_to_export")
    def test_export_to_xls_uses_prepare_to_export(self, mocked_prepare_to_export):
        temp = tempfile.NamedTemporaryFile(delete=False)
        self.files_to_delete.append(temp.name)
        encoding = "iso-8859-15"
        kwargs = {"test": 123, "parameter": 3.14}
        mocked_prepare_to_export.return_value = iter([utils.table.fields.keys()])

        rows.export_to_xls(utils.table, temp.name, encoding=encoding, **kwargs)
        assert mocked_prepare_to_export.called
        assert mocked_prepare_to_export.call_count == 1

        call = mocked_prepare_to_export.call_args
        assert call[0] == (utils.table,)
        kwargs["encoding"] = encoding
        assert call[1] == kwargs

    def test_issue_168(self):
        temp = tempfile.NamedTemporaryFile(delete=False)
        filename = "{}.{}".format(temp.name, self.file_extension)
        self.files_to_delete.append(filename)

        table = rows.Table(fields=OrderedDict([("jsoncolumn", rows.fields.JSONField)]))
        table.append({"jsoncolumn": '{"python": 42}'})
        rows.export_to_xls(table, filename)

        table2 = rows.import_from_xls(filename)
        self.assert_table_equal(table, table2)

    @mock.patch("rows.plugins.utils.create_table")
    def test_start_and_end_row(self, mocked_create_table):
        rows.import_from_xls(
            self.filename, start_row=6, end_row=8, start_column=6, end_column=8
        )
        assert mocked_create_table.called
        assert mocked_create_table.call_count == 1
        call_args = mocked_create_table.call_args_list[0]
        expected_data = [
            ["12.0%", "2050-01-02", "2050-01-02T23:45:31"],
            ["13.64%", "2015-08-18", "2015-08-18T22:21:33"],
            ["13.14%", "2015-03-04", "2015-03-04T16:00:01"],
        ]
        assert expected_data == list(call_args[0][0])

    def test_zero_date(self):
        table = rows.import_from_xls(
            "tests/data/empty-date.xls", force_types={"date": rows.fields.DateField}
        )

        assert len(table) == 5
        assert table[0].date == datetime.date(2000, 2, 3)
        assert table[1].date is None
        assert table[2].date == datetime.date(2001, 1, 2)
        assert table[3].date is None
        assert table[4].date == datetime.date(2001, 1, 2)

    def test_invalid_boundaries(self):
        table = rows.import_from_xls(
            "tests/data/all-field-types.xls",
            start_row=-10,
            end_row=100,
            start_column=-5,
            end_column=500,
        )
        assert len(table) == 7
        assert len(table.fields) == 8
