import json
import tempfile
from importlib import import_module
from io import StringIO
from pathlib import Path
from unittest import mock

from allianceauth.tests.auth_utils import AuthUtils
from django.conf import settings
from django.contrib.auth import SESSION_KEY
from django.core.management import call_command
from django.core.management.base import CommandError
from django.test import TestCase, override_settings

from shop.management.commands import shop_seed_demo as cmd
from shop.models import (
    BpcListing,
    FittedListing,
    HullListing,
    ModuleListing,
    Order,
    OrderLine,
    Shop,
)


def _owner(username="demoseller", char_id=90010):
    user = AuthUtils.create_user(username)
    AuthUtils.add_main_character_2(
        user, username, char_id, corp_id=2001, disconnect_signals=True
    )
    return user


def _evetype(type_id, name):
    """A minimal EveType so get_or_create_esi resolves it locally (no ESI call)."""
    from eveuniverse.models import EveCategory, EveGroup, EveType

    cat, _ = EveCategory.objects.get_or_create(
        id=999, defaults={"name": "Demo", "published": True}
    )
    grp, _ = EveGroup.objects.get_or_create(
        id=999, defaults={"name": "Demo", "eve_category": cat, "published": True}
    )
    et, _ = EveType.objects.get_or_create(
        id=type_id, defaults={"name": name, "eve_group": grp, "published": True}
    )
    return et


_FIXTURE = {
    "shop": {"name": "Demo Shop", "description": "**hi**"},
    "hulls": [[101, 12_000_000, 3, True], [102, 0, 0, False]],  # 2nd = build-to-order
    "modules": [[201, 5_000_000, 10, True]],
    "bpc": [
        {"type_id": 301, "featured": True, "variants": [[10, 20, 5, 2, 8_000_000]]}
    ],
    "fitted": [
        {
            "eve_type_id": 101,
            "ship_name": "Demo Fit",
            "price": 90_000_000,
            "featured": True,
            "fit_json": {
                "slots": [
                    {"flag": "HiSlot0", "type_id": 201, "name": "Gun", "charge": None}
                ],
                "drones": [],
                "cargo": [],
                "manifest": [],
            },
        }
    ],
}


class BundledDataTests(TestCase):
    def test_bundled_demo_data_is_valid(self):
        data = json.loads(cmd.DATA_PATH.read_text())
        for key in ("shop", "hulls", "modules", "bpc", "fitted"):
            self.assertTrue(data.get(key), f"missing/empty '{key}'")
        # every fit carries a reconstructed fit_json with slots
        self.assertTrue(
            all(
                isinstance(f.get("fit_json", {}).get("slots"), list)
                for f in data["fitted"]
            )
        )


@override_settings(DEBUG=True)
class GuardOwnerTests(TestCase):
    def test_unknown_user_errors(self):
        with self.assertRaises(CommandError):
            call_command("shop_seed_demo", "--user", "nobody")

    def test_no_main_character_errors(self):
        AuthUtils.create_user("nomain")
        with self.assertRaises(CommandError):
            call_command("shop_seed_demo", "--user", "nomain")

    def test_refuses_outside_debug_without_force(self):
        _owner()
        with override_settings(DEBUG=False):
            with self.assertRaises(CommandError):
                call_command("shop_seed_demo", "--user", "demoseller")

    def test_force_allows_run_outside_debug(self):
        _owner()
        for tid, name in [(101, "A"), (102, "B"), (201, "C"), (301, "D")]:
            _evetype(tid, name)
        with tempfile.NamedTemporaryFile("w", suffix=".json", delete=False) as fh:
            json.dump(_FIXTURE, fh)
            path = fh.name
        with override_settings(DEBUG=False):
            with mock.patch.object(cmd, "DATA_PATH", Path(path)):
                call_command("shop_seed_demo", "--user", "demoseller", "--force")
        self.assertTrue(Shop.objects.filter(slug="demo").exists())
        Path(path).unlink(missing_ok=True)


@override_settings(DEBUG=True)
class SeedTests(TestCase):
    def setUp(self):
        self.owner = _owner()
        for tid, name in [(101, "Rifter"), (102, "Vexor"), (201, "Gun"), (301, "BP")]:
            _evetype(tid, name)
        fh = tempfile.NamedTemporaryFile("w", suffix=".json", delete=False)
        json.dump(_FIXTURE, fh)
        fh.close()
        self._path = fh.name
        self._patch = mock.patch.object(cmd, "DATA_PATH", Path(self._path))
        self._patch.start()

    def tearDown(self):
        self._patch.stop()
        Path(self._path).unlink(missing_ok=True)

    def test_seeds_all_sections_and_orders(self):
        call_command("shop_seed_demo", "--user", "demoseller", "--slug", "demo")
        shop = Shop.objects.get(slug="demo")
        self.assertTrue(shop.is_published)
        self.assertEqual(
            set(shop.enabled_sections), {"hulls", "modules", "bpc", "fitted"}
        )
        self.assertEqual(HullListing.objects.filter(shop=shop).count(), 2)
        self.assertEqual(ModuleListing.objects.filter(shop=shop).count(), 1)
        self.assertEqual(BpcListing.objects.filter(shop=shop).count(), 1)
        self.assertEqual(FittedListing.objects.filter(shop=shop).count(), 1)
        self.assertGreaterEqual(Order.objects.filter(shop=shop).count(), 1)
        # the fitted order line carries the frozen fit
        line = OrderLine.objects.filter(order__shop=shop, section="fitted").first()
        self.assertIsNotNone(line)
        self.assertTrue(line.fit_json.get("slots"))

    def test_idempotent_across_reruns(self):
        call_command("shop_seed_demo", "--user", "demoseller")
        call_command("shop_seed_demo", "--user", "demoseller")  # again -> clean
        self.assertEqual(Shop.objects.filter(slug="demo").count(), 1)
        self.assertEqual(HullListing.objects.filter(shop__slug="demo").count(), 2)

    def test_print_session_round_trips_to_owner(self):
        out = StringIO()
        call_command(
            "shop_seed_demo", "--user", "demoseller", "--print-session", stdout=out
        )
        key = out.getvalue().strip().split()[-1]
        session = import_module(settings.SESSION_ENGINE).SessionStore(session_key=key)
        self.assertEqual(session.get(SESSION_KEY), str(self.owner.pk))
