# (c) cavaliba.com - tests / data / exporter per-instance loop (progress, abort, errors)

import io
from unittest import mock

from django.test import TestCase, override_settings

import app_home.cache as cache
from app_data.exporter import ExportSpec, export_count, export_queryset, export_write
from tests import helper

AAA = {"username": "unittest", "perms": ["p_data_admin"]}


class ExporterBatchTest(TestCase):
    def setUp(self):
        cache.clear()
        helper.add_admin_user(login="unittest")
        helper.add_schema(
            classname="expbatch",
            field_definition={
                "mystring": {"dataformat": "string", "displayname": "MyString"},
            },
        )
        for i in range(1, 8):
            helper.add_instance(
                classname="expbatch",
                keyname=f"expbatch-{i:02d}",
                fields={"mystring": f"row {i}"},
            )

    @override_settings(CAVALIBA_AUTH_MODE="unittest")
    def test_export_count_matches_rows(self):
        self.assertEqual(export_count(ExportSpec(schema="expbatch")), 7)

    @override_settings(CAVALIBA_AUTH_MODE="unittest")
    def test_export_count_honours_the_query(self):
        self.assertEqual(export_count(ExportSpec(schema="expbatch", query="mystring:row 3")), 1)

    @override_settings(CAVALIBA_AUTH_MODE="unittest")
    def test_queryset_is_ordered_by_id(self):
        qs = export_queryset(ExportSpec(schema="expbatch"))

        self.assertEqual(qs.query.order_by, ("id",))
        self.assertFalse(qs.query.is_sliced)

    @override_settings(CAVALIBA_AUTH_MODE="unittest")
    def test_stats_reported_for_a_clean_run(self):
        out = io.StringIO()
        stats = export_write(ExportSpec(schema="expbatch", format="csv"), AAA, out)

        self.assertEqual(stats["total"], 7)
        self.assertEqual(stats["count_done"], 7)
        self.assertEqual(stats["count_ok"], 7)
        self.assertEqual(stats["count_ko"], 0)
        self.assertEqual(stats["errors"], [])
        self.assertFalse(stats["aborted"])

    @override_settings(CAVALIBA_AUTH_MODE="unittest", CAVALIBA_MAX_EXPORT_ERROR=2)
    def test_too_many_failures_aborts_the_run(self):
        out = io.StringIO()
        with mock.patch("app_data.exporter.Instance", side_effect=ValueError("boom")):
            stats = export_write(ExportSpec(schema="expbatch", format="csv"), AAA, out)

        self.assertTrue(stats["aborted"])
        self.assertEqual(stats["count_ok"], 0)
        self.assertEqual(stats["count_ko"], 3)
        self.assertEqual(len(stats["errors"]), 3)
        self.assertIn("boom", stats["errors"][0])

    @override_settings(CAVALIBA_AUTH_MODE="unittest", CAVALIBA_MAX_EXPORT_ERROR=100)
    def test_a_failing_instance_is_counted_not_fatal(self):
        real_instance = __import__("app_data.data", fromlist=["Instance"]).Instance
        calls = {"n": 0}

        def flaky(*args, **kwargs):
            calls["n"] += 1
            if calls["n"] == 2:
                raise ValueError("bad row")
            return real_instance(*args, **kwargs)

        out = io.StringIO()
        with mock.patch("app_data.exporter.Instance", side_effect=flaky):
            stats = export_write(ExportSpec(schema="expbatch", format="csv"), AAA, out)

        self.assertFalse(stats["aborted"])
        self.assertEqual(stats["count_ok"], 6)
        self.assertEqual(stats["count_ko"], 1)
        self.assertEqual(len(out.getvalue().splitlines()), 7)

    @override_settings(CAVALIBA_AUTH_MODE="unittest")
    def test_on_batch_called_at_batch_size(self):
        seen = []

        def on_batch(count_done, total, count_ok, count_ko):
            seen.append((count_done, total, count_ok, count_ko))

        out = io.StringIO()
        with mock.patch("app_data.exporter.BATCH_SIZE", 3):
            export_write(ExportSpec(schema="expbatch", format="csv"), AAA, out, on_batch=on_batch)

        self.assertEqual([s[0] for s in seen], [3, 6])
        self.assertEqual(seen[0][1], 7)
        self.assertEqual(seen[0][2], 3)

    @override_settings(CAVALIBA_AUTH_MODE="unittest")
    def test_on_batch_abort_stops_the_run(self):
        def on_batch(count_done, total, count_ok, count_ko):
            return "abort"

        out = io.StringIO()
        with mock.patch("app_data.exporter.BATCH_SIZE", 3):
            stats = export_write(
                ExportSpec(schema="expbatch", format="csv"), AAA, out, on_batch=on_batch
            )

        self.assertTrue(stats["aborted"])
        self.assertEqual(stats["count_done"], 3)
        self.assertEqual(stats["count_ok"], 3)

    @override_settings(CAVALIBA_AUTH_MODE="unittest")
    def test_unreadable_instances_are_scanned_but_not_written(self):
        out = io.StringIO()
        with mock.patch("app_data.data.Instance.has_read_permission", return_value=False):
            stats = export_write(ExportSpec(schema="expbatch", format="csv"), AAA, out)

        self.assertEqual(stats["total"], 7)
        self.assertEqual(stats["count_done"], 7)
        self.assertEqual(stats["count_ok"], 0)
        self.assertEqual(stats["count_ko"], 0)
