from django.test import Client, TestCase

from dice.models import DieFaceStat

from .models import DecathlonScore


class RollViewTests(TestCase):
    def setUp(self):
        self.client = Client()

    def test_roll_returns_exactly_n_dice_with_valid_values(self):
        response = self.client.get('/decathlon/roll', {'n': '5'})
        self.assertEqual(response.status_code, 200)
        data = response.json()
        self.assertEqual(len(data['values']), 5)
        self.assertTrue(all(1 <= v <= 6 for v in data['values']))

    def test_roll_caps_n_at_eight(self):
        response = self.client.get('/decathlon/roll', {'n': '99'})
        self.assertEqual(len(response.json()['values']), 8)

    def test_roll_zero_returns_no_dice(self):
        response = self.client.get('/decathlon/roll', {'n': '0'})
        self.assertEqual(response.json()['values'], [])

    def test_roll_increments_shared_dicefacestat(self):
        before = sum(row.ever_count for row in DieFaceStat.objects.filter(dice_type='d6'))
        self.client.get('/decathlon/roll', {'n': '5'})
        after = sum(row.ever_count for row in DieFaceStat.objects.filter(dice_type='d6'))
        self.assertEqual(after - before, 5)


class IndexViewTests(TestCase):
    def setUp(self):
        self.client = Client()

    def test_index_renders_score_sheet_and_event_rows(self):
        response = self.client.get('/decathlon/')
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, 'Score Sheet')
        self.assertContains(response, 'id="row_hurdles"')
        self.assertContains(response, 'id="g_hurdles"')
        self.assertContains(response, '<wa-dialog id="leaderboard"')
        self.assertContains(response, '<wa-switch id="pip_style"')
        self.assertContains(response, '<wa-accordion id="last_rolls"')
        self.assertContains(response, '<wa-input')
        self.assertContains(response, '<wa-badge')
        self.assertContains(response, 'id="a_shot_put_1"')
        self.assertNotContains(response, '<button')
        self.assertNotContains(response, '<details')
        self.assertNotContains(response, '<input')

    def test_index_has_no_coming_soon_events_left(self):
        response = self.client.get('/decathlon/')
        self.assertNotContains(response, 'coming-soon')

    def test_index_renders_m100_as_active(self):
        response = self.client.get('/decathlon/')
        content = response.content.decode()
        row_start = content.index('id="row_m100"')
        row_end = content.index('</tr>', row_start)
        row_html = content[row_start:row_end]
        self.assertNotIn('coming-soon', row_html)
        self.assertIn('id="g_m100"', row_html)
        self.assertNotIn('id="a_m100_1"', row_html)

    def test_index_renders_shot_put_as_active_with_attempt_boxes(self):
        response = self.client.get('/decathlon/')
        content = response.content.decode()
        row_start = content.index('id="row_shot_put"')
        row_end = content.index('</tr>', row_start)
        row_html = content[row_start:row_end]
        self.assertNotIn('coming-soon', row_html)
        self.assertIn('id="a_shot_put_1"', row_html)
        self.assertIn('id="a_shot_put_2"', row_html)
        self.assertIn('id="a_shot_put_3"', row_html)
        self.assertIn('id="g_shot_put"', row_html)

    def test_index_renders_m400_as_active(self):
        response = self.client.get('/decathlon/')
        content = response.content.decode()
        row_start = content.index('id="row_m400"')
        row_end = content.index('</tr>', row_start)
        row_html = content[row_start:row_end]
        self.assertNotIn('coming-soon', row_html)
        self.assertIn('id="g_m400"', row_html)
        self.assertNotIn('id="a_m400_1"', row_html)

    def test_index_renders_m1500_as_active(self):
        response = self.client.get('/decathlon/')
        content = response.content.decode()
        row_start = content.index('id="row_m1500"')
        row_end = content.index('</tr>', row_start)
        row_html = content[row_start:row_end]
        self.assertNotIn('coming-soon', row_html)
        self.assertIn('id="g_m1500"', row_html)
        self.assertNotIn('id="a_m1500_1"', row_html)

    def test_index_renders_discus_as_active_with_attempt_boxes(self):
        response = self.client.get('/decathlon/')
        content = response.content.decode()
        row_start = content.index('id="row_discus"')
        row_end = content.index('</tr>', row_start)
        row_html = content[row_start:row_end]
        self.assertNotIn('coming-soon', row_html)
        self.assertIn('id="a_discus_1"', row_html)
        self.assertIn('id="a_discus_2"', row_html)
        self.assertIn('id="a_discus_3"', row_html)
        self.assertIn('id="g_discus"', row_html)

    def test_index_renders_javelin_as_active_with_attempt_boxes(self):
        response = self.client.get('/decathlon/')
        content = response.content.decode()
        row_start = content.index('id="row_javelin"')
        row_end = content.index('</tr>', row_start)
        row_html = content[row_start:row_end]
        self.assertNotIn('coming-soon', row_html)
        self.assertIn('id="a_javelin_1"', row_html)
        self.assertIn('id="a_javelin_2"', row_html)
        self.assertIn('id="a_javelin_3"', row_html)
        self.assertIn('id="g_javelin"', row_html)

    def test_index_renders_pole_vault_as_active(self):
        response = self.client.get('/decathlon/')
        content = response.content.decode()
        row_start = content.index('id="row_pole_vault"')
        row_end = content.index('</tr>', row_start)
        row_html = content[row_start:row_end]
        self.assertNotIn('coming-soon', row_html)
        self.assertIn('id="g_pole_vault"', row_html)
        self.assertNotIn('id="a_pole_vault_1"', row_html)

    def test_index_renders_high_jump_as_active(self):
        response = self.client.get('/decathlon/')
        content = response.content.decode()
        row_start = content.index('id="row_high_jump"')
        row_end = content.index('</tr>', row_start)
        row_html = content[row_start:row_end]
        self.assertNotIn('coming-soon', row_html)
        self.assertIn('id="g_high_jump"', row_html)
        self.assertNotIn('id="a_high_jump_1"', row_html)

    def test_index_renders_long_jump_as_active_with_attempt_boxes(self):
        response = self.client.get('/decathlon/')
        content = response.content.decode()
        row_start = content.index('id="row_long_jump"')
        row_end = content.index('</tr>', row_start)
        row_html = content[row_start:row_end]
        self.assertNotIn('coming-soon', row_html)
        self.assertIn('id="a_long_jump_1"', row_html)
        self.assertIn('id="a_long_jump_2"', row_html)
        self.assertIn('id="a_long_jump_3"', row_html)
        self.assertIn('id="g_long_jump"', row_html)


class SaveScoreViewTests(TestCase):
    def setUp(self):
        self.client = Client()

    def test_save_score_creates_row_with_name_and_marks_it_new(self):
        response = self.client.get('/decathlon/save_score', {'total': '180', 'name': 'Dan'})
        self.assertEqual(response.status_code, 200)
        self.assertEqual(DecathlonScore.objects.count(), 1)
        data = response.json()
        self.assertEqual(data['new_rank'], 1)
        self.assertEqual(data['scores'][0]['total'], 180)
        self.assertEqual(data['scores'][0]['name'], 'Dan')
        self.assertTrue(data['scores'][0]['is_new'])

    def test_save_score_blank_name_defaults_to_anonymous(self):
        response = self.client.get('/decathlon/save_score', {'total': '50', 'name': '   '})
        self.assertEqual(response.json()['scores'][0]['name'], 'Anonymous')

    def test_save_score_orders_by_total_descending(self):
        DecathlonScore.objects.create(name='a', total=100)
        DecathlonScore.objects.create(name='b', total=300)
        response = self.client.get('/decathlon/save_score', {'total': '200', 'name': 'c'})
        totals = [row['total'] for row in response.json()['scores']]
        self.assertEqual(totals, [300, 200, 100])

    def test_save_score_list_capped_at_100(self):
        DecathlonScore.objects.bulk_create(
            [DecathlonScore(name='x', total=i) for i in range(150)]
        )
        response = self.client.get('/decathlon/save_score', {'total': '9999', 'name': 'top'})
        self.assertEqual(len(response.json()['scores']), 100)

    def test_save_score_invalid_total_returns_empty(self):
        response = self.client.get('/decathlon/save_score', {'total': 'abc'})
        self.assertEqual(response.json(), {'scores': [], 'new_rank': None})
        self.assertEqual(DecathlonScore.objects.count(), 0)
