"""
test_views.py
Tests for the events registration flow including mail notifications.
"""

from datetime import timedelta

from django.contrib.auth import get_user_model
from django.contrib.auth.models import Permission
from django.core import mail
from django.test import TestCase, Client
from django.urls import reverse
from django.utils import timezone

from events.models import Event, Tournament, Registration


def _make_event(registration_mail=None):
    return Event.objects.create(
        name='Tortenturnier',
        subtitle='Das Turnier',
        description='Beschreibung',
        begin=timezone.now(),
        registration_mail=registration_mail,
    )


def _make_tournament(event, registration_active=True):
    return Tournament.objects.create(
        name='Hauptfeld',
        gender='m',
        leagues='A/B',
        begin=timezone.now(),
        event=event,
        max_teams=10,
        registration_active=registration_active,
    )


VALID_POST = {
    'club': 'Sportverein 01',
    'team': 'Herren 1',
    'responsible_firstname': 'Max',
    'responsible_lastname': 'Mustermann',
    'phone': '+49 123 456789',
    'mail': 'max@example.com',
}


class RegistrationNotificationTests(TestCase):
    """
    Tests that correct emails are sent on registration.
    """

    client = Client()

    def test_two_mails_sent_when_event_mail_configured(self):
        """Both registrant and organizer mail are sent when registration_mail is set."""
        event = _make_event(registration_mail='tortenturnier@vcks.de')
        tournament = _make_tournament(event)

        with self.captureOnCommitCallbacks(execute=True):
            self.client.post(
                reverse('events:register', kwargs={'tournament_id': tournament.pk}),
                data=VALID_POST,
            )

        self.assertEqual(len(mail.outbox), 2)
        recipients = {m.to[0] for m in mail.outbox}
        self.assertIn('max@example.com', recipients)
        self.assertIn('tortenturnier@vcks.de', recipients)

    def test_one_mail_sent_when_no_event_mail_configured(self):
        """Only the registrant mail is sent when registration_mail is not set."""
        event = _make_event(registration_mail=None)
        tournament = _make_tournament(event)

        with self.captureOnCommitCallbacks(execute=True):
            self.client.post(
                reverse('events:register', kwargs={'tournament_id': tournament.pk}),
                data=VALID_POST,
            )

        self.assertEqual(len(mail.outbox), 1)
        self.assertEqual(mail.outbox[0].to, ['max@example.com'])

    def test_registrant_mail_subject_contains_event_and_tournament_name(self):
        """Registrant mail subject includes both event and tournament names."""
        event = _make_event()
        tournament = _make_tournament(event)

        with self.captureOnCommitCallbacks(execute=True):
            self.client.post(
                reverse('events:register', kwargs={'tournament_id': tournament.pk}),
                data=VALID_POST,
            )

        self.assertEqual(len(mail.outbox), 1)
        self.assertIn('Tortenturnier', mail.outbox[0].subject)
        self.assertIn('Hauptfeld', mail.outbox[0].subject)

    def test_organizer_mail_subject_contains_event_and_tournament_name(self):
        """Organizer mail subject includes both event and tournament names."""
        event = _make_event(registration_mail='tortenturnier@vcks.de')
        tournament = _make_tournament(event)

        with self.captureOnCommitCallbacks(execute=True):
            self.client.post(
                reverse('events:register', kwargs={'tournament_id': tournament.pk}),
                data=VALID_POST,
            )

        organizer_mail = next(m for m in mail.outbox if m.to == ['tortenturnier@vcks.de'])
        self.assertIn('Tortenturnier', organizer_mail.subject)
        self.assertIn('Hauptfeld', organizer_mail.subject)

    def test_registration_saved_even_when_mail_backend_is_dummy(self):
        """DB registration is created regardless of mail delivery."""
        event = _make_event(registration_mail='tortenturnier@vcks.de')
        tournament = _make_tournament(event)

        with self.settings(EMAIL_BACKEND='django.core.mail.backends.dummy.EmailBackend'):
            with self.captureOnCommitCallbacks(execute=True):
                response = self.client.post(
                    reverse('events:register', kwargs={'tournament_id': tournament.pk}),
                    data=VALID_POST,
                )

        self.assertEqual(Registration.objects.filter(tournament=tournament).count(), 1)
        self.assertEqual(response.status_code, 302)

    def test_successful_registration_redirects_to_event_with_success_message(self):
        """Successful registration redirects to event page and shows feedback."""
        event = _make_event(registration_mail='tortenturnier@vcks.de')
        tournament = _make_tournament(event)

        with self.captureOnCommitCallbacks(execute=True):
            response = self.client.post(
                reverse('events:register', kwargs={'tournament_id': tournament.pk}),
                data=VALID_POST,
                follow=True,
            )

        self.assertRedirects(response, reverse('events:event', kwargs={'event_id': event.pk}))
        self.assertContains(response, 'Die Anmeldung wurde erfolgreich gesendet.')

    def test_organizer_mail_body_contains_full_contact_data(self):
        """Organizer mail body includes all contact fields."""
        event = _make_event(registration_mail='tortenturnier@vcks.de')
        tournament = _make_tournament(event)

        with self.captureOnCommitCallbacks(execute=True):
            self.client.post(
                reverse('events:register', kwargs={'tournament_id': tournament.pk}),
                data=VALID_POST,
            )

        organizer_mail = next(m for m in mail.outbox if m.to == ['tortenturnier@vcks.de'])
        self.assertIn('Sportverein 01', organizer_mail.body)
        self.assertIn('Herren 1', organizer_mail.body)
        self.assertIn('Max', organizer_mail.body)
        self.assertIn('Mustermann', organizer_mail.body)
        self.assertIn('+49 123 456789', organizer_mail.body)
        self.assertIn('max@example.com', organizer_mail.body)


class EventDetailTests(TestCase):
    """Tests for the event detail page."""

    def test_tournaments_are_sorted_by_date(self):
        """Event detail shows tournaments in ascending begin order."""
        event = _make_event()
        now = timezone.now()

        late = Tournament.objects.create(
            name='Später',
            gender='m',
            leagues='A',
            begin=now + timedelta(days=2),
            event=event,
            max_teams=10,
            registration_active=True,
        )
        early = Tournament.objects.create(
            name='Früher',
            gender='m',
            leagues='A',
            begin=now + timedelta(days=1),
            event=event,
            max_teams=10,
            registration_active=True,
        )

        response = self.client.get(reverse('events:event', kwargs={'event_id': event.pk}))

        tournaments = list(response.context['tournaments'])
        self.assertEqual([t.pk for t in tournaments], [early.pk, late.pk])

    def test_registration_list_link_hidden_for_anonymous_user(self):
        """Anonymous users should not see the registration list link."""
        event = _make_event()
        tournament = _make_tournament(event)

        response = self.client.get(reverse('events:event', kwargs={'event_id': event.pk}))

        registration_list_url = reverse(
            'events:registrationList',
            kwargs={'tournament_id': tournament.pk},
        )
        self.assertNotContains(response, registration_list_url)

    def test_registration_list_link_visible_for_logged_in_user(self):
        """Logged-in users should see the registration list link."""
        event = _make_event()
        tournament = _make_tournament(event)
        user = get_user_model().objects.create_user(
            username='event-view-user',
            password='test-pass-123',
        )
        self.client.force_login(user)

        response = self.client.get(reverse('events:event', kwargs={'event_id': event.pk}))

        registration_list_url = reverse(
            'events:registrationList',
            kwargs={'tournament_id': tournament.pk},
        )
        self.assertContains(response, registration_list_url)


class RegistrationListAccessTests(TestCase):
    """Access control tests for registration list."""

    def test_registration_list_redirects_anonymous_user_to_login(self):
        """Anonymous users must not see registrations."""
        event = _make_event()
        tournament = _make_tournament(event)

        response = self.client.get(
            reverse('events:registrationList', kwargs={'tournament_id': tournament.pk})
        )

        self.assertEqual(response.status_code, 302)
        self.assertIn('/accounts/login/', response.url)

    def test_registration_list_is_accessible_for_logged_in_user(self):
        """Logged-in users can view the registration list."""
        event = _make_event()
        tournament = _make_tournament(event)
        user = get_user_model().objects.create_user(
            username='testuser',
            password='test-pass-123',
        )
        self.client.force_login(user)

        response = self.client.get(
            reverse('events:registrationList', kwargs={'tournament_id': tournament.pk})
        )

        self.assertEqual(response.status_code, 200)


class RegistrationDeleteTests(TestCase):
    """Access control and behavior tests for registration deletion."""

    def setUp(self):
        self.event = _make_event()
        self.tournament = _make_tournament(self.event)
        self.registration = Registration.objects.create(
            tournament=self.tournament,
            **VALID_POST,
        )
        self.url = reverse(
            'events:registrationDelete',
            kwargs={
                'tournament_id': self.tournament.pk,
                'registration_id': self.registration.pk,
            },
        )

    def test_registration_delete_redirects_anonymous_user_to_login(self):
        """Anonymous users cannot delete registrations."""
        response = self.client.post(self.url)

        self.assertEqual(response.status_code, 302)
        self.assertIn('/accounts/login/', response.url)
        self.assertTrue(Registration.objects.filter(pk=self.registration.pk).exists())

    def test_registration_delete_returns_403_without_permission(self):
        """Logged-in users without delete permission are denied."""
        user = get_user_model().objects.create_user(
            username='registration-delete-no-perm',
            password='test-pass-123',
        )
        self.client.force_login(user)

        response = self.client.post(self.url)

        self.assertEqual(response.status_code, 403)
        self.assertTrue(Registration.objects.filter(pk=self.registration.pk).exists())

    def test_registration_delete_rejects_get_requests(self):
        """Deletion endpoint only accepts POST requests."""
        user = get_user_model().objects.create_user(
            username='registration-delete-get',
            password='test-pass-123',
        )
        user.user_permissions.add(Permission.objects.get(codename='delete_registration'))
        self.client.force_login(user)

        response = self.client.get(self.url)

        self.assertEqual(response.status_code, 405)
        self.assertTrue(Registration.objects.filter(pk=self.registration.pk).exists())

    def test_registration_delete_removes_registration_with_permission(self):
        """Users with delete permission can remove a registration."""
        user = get_user_model().objects.create_user(
            username='registration-delete-ok',
            password='test-pass-123',
        )
        user.user_permissions.add(Permission.objects.get(codename='delete_registration'))
        self.client.force_login(user)

        response = self.client.post(self.url)

        self.assertEqual(response.status_code, 302)
        self.assertEqual(
            response.url,
            reverse('events:registrationList', kwargs={'tournament_id': self.tournament.pk}),
        )
        self.assertFalse(Registration.objects.filter(pk=self.registration.pk).exists())

    def test_registration_delete_button_hidden_without_permission(self):
        """Registration list does not render delete action without permission."""
        user = get_user_model().objects.create_user(
            username='registration-list-no-delete-button',
            password='test-pass-123',
        )
        self.client.force_login(user)

        response = self.client.get(
            reverse('events:registrationList', kwargs={'tournament_id': self.tournament.pk})
        )

        delete_url = reverse(
            'events:registrationDelete',
            kwargs={
                'tournament_id': self.tournament.pk,
                'registration_id': self.registration.pk,
            },
        )
        self.assertNotContains(response, delete_url)
        self.assertNotContains(response, 'Loeschen')

    def test_registration_delete_button_visible_with_permission(self):
        """Registration list renders delete action with delete permission."""
        user = get_user_model().objects.create_user(
            username='registration-list-delete-button',
            password='test-pass-123',
        )
        user.user_permissions.add(Permission.objects.get(codename='delete_registration'))
        self.client.force_login(user)

        response = self.client.get(
            reverse('events:registrationList', kwargs={'tournament_id': self.tournament.pk})
        )

        delete_url = reverse(
            'events:registrationDelete',
            kwargs={
                'tournament_id': self.tournament.pk,
                'registration_id': self.registration.pk,
            },
        )
        self.assertContains(response, delete_url)
        self.assertContains(response, 'Loeschen')

    def test_registration_delete_confirmation_dialog(self):
        """Registration list renders confirmation dialog for delete action."""
        user = get_user_model().objects.create_user(
            username='registration-confirm-dialog',
            password='test-pass-123',
        )
        user.user_permissions.add(Permission.objects.get(codename='delete_registration'))
        self.client.force_login(user)

        response = self.client.get(
            reverse('events:registrationList', kwargs={'tournament_id': self.tournament.pk})
        )

        # Check that the form has an onsubmit confirmation dialog
        self.assertContains(response, 'onsubmit=')
        self.assertContains(response, 'confirm(')
        self.assertContains(response, 'wirklich löschen')



class RegistrationDuplicatePreventionTests(TestCase):
    """Prevent duplicate registrations for the same club+team within a tournament."""

    def setUp(self):
        self.event = _make_event()
        self.tournament = _make_tournament(self.event)

    def test_cannot_register_same_club_and_team_twice(self):
        """Same club and team cannot register twice."""
        form_data = {
            'club': 'Sportverein 01',
            'team': 'Herren 1',
            'responsible_firstname': 'Max',
            'responsible_lastname': 'Mustermann',
            'phone': '+49 123 456789',
            'mail': 'max@example.com',
        }

        response1 = self.client.post(
            reverse('events:register', kwargs={'tournament_id': self.tournament.pk}),
            data=form_data,
        )
        self.assertEqual(response1.status_code, 302)
        self.assertEqual(Registration.objects.filter(tournament=self.tournament).count(), 1)

        self.client.post(
            reverse('events:register', kwargs={'tournament_id': self.tournament.pk}),
            data=form_data,
        )
        self.assertEqual(
            Registration.objects.filter(tournament=self.tournament).count(),
            1,
            'Duplicate registration should not be saved'
        )

    def test_can_register_different_club_same_team(self):
        """Different club with same team name can register."""
        first_form = {
            'club': 'Verein A',
            'team': 'Herren 1',
            'responsible_firstname': 'Max',
            'responsible_lastname': 'Mustermann',
            'phone': '+49 123 456789',
            'mail': 'max@example.com',
        }
        second_form = {
            'club': 'Verein B',
            'team': 'Herren 1',
            'responsible_firstname': 'Anna',
            'responsible_lastname': 'Schmidt',
            'phone': '+49 987 654321',
            'mail': 'anna@example.com',
        }

        self.client.post(
            reverse('events:register', kwargs={'tournament_id': self.tournament.pk}),
            data=first_form,
        )
        self.client.post(
            reverse('events:register', kwargs={'tournament_id': self.tournament.pk}),
            data=second_form,
        )

        self.assertEqual(
            Registration.objects.filter(tournament=self.tournament).count(),
            2,
            'Different clubswith same team should be allowed'
        )

    def test_can_register_same_club_different_team(self):
        """Same club with different team can register."""
        first_form = {
            'club': 'Sportverein 01',
            'team': 'Herren 1',
            'responsible_firstname': 'Max',
            'responsible_lastname': 'Mustermann',
            'phone': '+49 123 456789',
            'mail': 'max@example.com',
        }
        second_form = {
            'club': 'Sportverein 01',
            'team': 'Damen 1',
            'responsible_firstname': 'Anna',
            'responsible_lastname': 'Schmidt',
            'phone': '+49 987 654321',
            'mail': 'anna@example.com',
        }

        self.client.post(
            reverse('events:register', kwargs={'tournament_id': self.tournament.pk}),
            data=first_form,
        )
        self.client.post(
            reverse('events:register', kwargs={'tournament_id': self.tournament.pk}),
            data=second_form,
        )

        self.assertEqual(
            Registration.objects.filter(tournament=self.tournament).count(),
            2,
            'Same club with different teams should be allowed'
        )

    def test_duplicate_error_message_shown_on_form(self):
        """Form displays user-friendly error for duplicate registration."""
        form_data = {
            'club': 'Sportverein 01',
            'team': 'Herren 1',
            'responsible_firstname': 'Max',
            'responsible_lastname': 'Mustermann',
            'phone': '+49 123 456789',
            'mail': 'max@example.com',
        }

        self.client.post(
            reverse('events:register', kwargs={'tournament_id': self.tournament.pk}),
            data=form_data,
        )

        response = self.client.post(
            reverse('events:register', kwargs={'tournament_id': self.tournament.pk}),
            data=form_data,
        )

        self.assertContains(response, 'bereits für dieses Turnier angemeldet')

    def test_same_club_team_allowed_in_different_tournament(self):
        """Same club+team can register in different tournaments."""
        tournament2 = _make_tournament(self.event)

        form_data = {
            'club': 'Sportverein 01',
            'team': 'Herren 1',
            'responsible_firstname': 'Max',
            'responsible_lastname': 'Mustermann',
            'phone': '+49 123 456789',
            'mail': 'max@example.com',
        }

        self.client.post(
            reverse('events:register', kwargs={'tournament_id': self.tournament.pk}),
            data=form_data,
        )
        self.client.post(
            reverse('events:register', kwargs={'tournament_id': tournament2.pk}),
            data=form_data,
        )

        self.assertEqual(Registration.objects.filter(club='Sportverein 01').count(), 2)
