from collections import defaultdict

from moonmining.models import Moon, MoonProduct

from django.test import TestCase
from eveuniverse.models import EveSolarSystem, EveType

from metenox.models import Moon as MetenoxMoon
from metenox.models import MoonTag
from metenox.moons import get_metenox_hourly_harvest
from metenox.tasks import find_all_moon_systems_within_range, tag_moons_in_range

from ..tasks import create_moon_from_moonmining, update_moons_from_moonmining
from .testdata.load_eveuniverse import load_eveuniverse

MOON_ID = 40178441


class TestMoons(TestCase):

    @classmethod
    def setUpClass(cls):
        super().setUpClass()
        load_eveuniverse()

    def test_wh_moon_hourly_pull(self):
        """Hourly pull of a wormhole moon (only r4)"""
        # TODO
        self.assertTrue(True)

    def test_no_crash_querrying_moon_not_in_moonmining(self):
        """Checks that there's no error returned if you ask for a moon not in the moonmiming application"""
        get_metenox_hourly_harvest(MOON_ID)

    def test_kspace_moon_hourly_pull(self):
        """Hourly pull of an R16 moon"""
        moon_scan: dict[int, float] = {
            45501: 0.303093851,
            45497: 0.088907719,
            45499: 0.307283372,
            45490: 0.300715059,
        }

        moon, _ = Moon.objects.get_or_create(eve_moon_id=MOON_ID)
        moon_products = [
            MoonProduct(moon=moon, ore_type_id=ore_type_id, amount=ore_type_amount)
            for ore_type_id, ore_type_amount in moon_scan.items()
        ]

        moon.update_products(moon_products)

        harvest = get_metenox_hourly_harvest(moon.eve_moon_id)

        expected = defaultdict(int)
        expected.update(
            {
                EveType.objects.get(id=16634): 234,
                EveType.objects.get(id=16641): 145,
                EveType.objects.get(id=16635): 36,
                EveType.objects.get(id=16633): 36,
                EveType.objects.get(id=16644): 147,
                EveType.objects.get(id=16637): 42,
            }
        )

        self.assertDictEqual(harvest, expected)

    def test_double_r64_hourly_pull(self):
        """
        Test on a moon with double r64
        Way more moon materials present there
        """

        moon_scan = {
            45512: 0.210578531,
            45497: 0.272598475,
            45499: 0.296382815,
            45510: 0.220440164,
        }

        moon, _ = Moon.objects.get_or_create(eve_moon_id=MOON_ID)
        moon_products = [
            MoonProduct(moon=moon, ore_type_id=ore_type_id, amount=ore_type_amount)
            for ore_type_id, ore_type_amount in moon_scan.items()
        ]

        moon.update_products(moon_products)

        harvest = get_metenox_hourly_harvest(moon.eve_moon_id)

        expected = defaultdict(int)
        expected.update(
            {
                EveType.objects.get(id=16634): 52,
                EveType.objects.get(id=16640): 52,
                EveType.objects.get(id=16650): 58,
                EveType.objects.get(id=16635): 35,
                EveType.objects.get(id=16633): 50,
                EveType.objects.get(id=16644): 167,
                EveType.objects.get(id=16652): 55,
                EveType.objects.get(id=16639): 50,
                EveType.objects.get(id=16637): 130,
                EveType.objects.get(id=16642): 26,
            }
        )

        self.assertDictEqual(harvest, expected)

    def test_integrity_error_on_updating_existing_moon(self):
        """
        Integrity error issue when running an update on an already existing moon
        """

        moon_scan = {
            45490: 0.2809781134,
            45491: 0.2083230466,
            45493: 0.5106988549,
        }

        moonmining_moon, _ = Moon.objects.get_or_create(eve_moon_id=MOON_ID)
        moon_products = [
            MoonProduct(
                moon=moonmining_moon, ore_type_id=ore_type_id, amount=ore_type_amount
            )
            for ore_type_id, ore_type_amount in moon_scan.items()
        ]

        moonmining_moon.update_products(moon_products)

        create_moon_from_moonmining(MOON_ID)
        create_moon_from_moonmining(MOON_ID)

    def test_create_moon_mats_on_existing_moon(self):
        """
        When a moon is created without moon materials `create_or_update_moon_from_moonmining` needs to create them
        """

        moon_scan = {
            45490: 0.2809781134,
            45491: 0.2083230466,
            45493: 0.5106988549,
        }

        moonmining_moon, _ = Moon.objects.get_or_create(eve_moon_id=MOON_ID)
        moon_products = [
            MoonProduct(
                moon=moonmining_moon, ore_type_id=ore_type_id, amount=ore_type_amount
            )
            for ore_type_id, ore_type_amount in moon_scan.items()
        ]

        moonmining_moon.update_products(moon_products)
        metenox_moon = MetenoxMoon.objects.create(
            eve_moon_id=MOON_ID,
            moonmining_moon=moonmining_moon,
        )

        update_moons_from_moonmining()

        pull = metenox_moon.hourly_pull

        self.assertEqual(len(pull), 3)

    def test_find_moons_that_need_update(self):
        """
        Will check if it's correctly finding moons with products in the moonmining application but none in the metenox application
        """

        moon_scan = {
            45490: 0.2809781134,
            45491: 0.2083230466,
            45493: 0.5106988549,
        }

        moonmining_moon, _ = Moon.objects.get_or_create(eve_moon_id=MOON_ID)

        metenox_moon = MetenoxMoon.objects.create(
            eve_moon_id=MOON_ID,
            moonmining_moon=moonmining_moon,
        )

        moon_products = [
            MoonProduct(
                moon=moonmining_moon, ore_type_id=ore_type_id, amount=ore_type_amount
            )
            for ore_type_id, ore_type_amount in moon_scan.items()
        ]
        moonmining_moon.update_products(moon_products)

        moon_to_update = MetenoxMoon.moons_in_need_of_update()

        self.assertEqual(list(moon_to_update), [metenox_moon])

    def test_no_crash_cycles_before_full_empty_moon(self):
        """
        Checks that a moon without a survey in moonmining won't crash when cycles_before_full is called
        """

        moonmining_moon, _ = Moon.objects.get_or_create(eve_moon_id=MOON_ID)

        metenox_moon = MetenoxMoon.objects.create(
            eve_moon_id=MOON_ID,
            moonmining_moon=moonmining_moon,
        )

        metenox_moon.cycles_before_full

    def test_moon_systems_in_range(self):
        tama = EveSolarSystem.objects.get(id=30002813)
        tama_moon = MetenoxMoon.objects.create(
            eve_moon_id=40178441,
            moonmining_moon=Moon.objects.get_or_create(eve_moon_id=40178441)[0],
        )
        onatoh_moon = MetenoxMoon.objects.create(
            eve_moon_id=40178312,
            moonmining_moon=Moon.objects.get_or_create(eve_moon_id=40178312)[0],
        )
        MetenoxMoon.objects.create(
            eve_moon_id=40471667,
            moonmining_moon=Moon.objects.get_or_create(eve_moon_id=40471667)[0],
        )

        moon_systems_in_range = find_all_moon_systems_within_range(tama, 1.0)

        self.assertEqual(len(moon_systems_in_range), 2)
        self.assertIn(
            tama_moon.eve_moon.eve_planet.eve_solar_system, moon_systems_in_range
        )
        self.assertIn(
            onatoh_moon.eve_moon.eve_planet.eve_solar_system, moon_systems_in_range
        )

    def test_tag_moons_in_range(self):
        tama = EveSolarSystem.objects.get(id=30002813)
        MetenoxMoon.objects.create(
            eve_moon_id=40178441,
            moonmining_moon=Moon.objects.get_or_create(eve_moon_id=40178441)[0],
        )
        MetenoxMoon.objects.create(
            eve_moon_id=40178312,
            moonmining_moon=Moon.objects.get_or_create(eve_moon_id=40178312)[0],
        )

        tag = MoonTag.objects.create(name="Test tag")

        tag_moons_in_range(tama.id, 1.0, tag.id)

        self.assertEqual(2, tag.moons.count())
