from django.urls import reverse
from django.test import TestCase

# Create your tests here.


class SettingsPageNoAutoScrollTests(TestCase):
    def test_settings_page_has_no_autofocus_field(self):
        from django.urls import reverse

        from accounts.models import User

        user = User.objects.create_user(username='u1', email='u1@x.com', password='pw12345!', role=User.Role.SEEKER)
        self.client.force_login(user)
        html = self.client.get(reverse('accounts:settings')).content.decode()
        self.assertNotIn('autofocus', html)


class DashboardSettingsTests(TestCase):
    def setUp(self):
        from accounts.models import User
        from companies.models import Company
        from seekers.models import SeekerProfile

        self.seeker = User.objects.create_user(username='sk', email='sk@x.com', password='OldPass123!', role=User.Role.SEEKER,
                                               first_name='Sam', last_name='Seeker')
        self.profile = SeekerProfile.objects.create(user=self.seeker, slug='sam-s', full_name='Sam Seeker', headline='Baker',
                                                    category='x', category_slug='x', location='Lusaka', skills='Baking', is_public=True)
        self.employer = User.objects.create_user(username='em', email='em@x.com', password='OldPass123!', role=User.Role.COMPANY)
        self.company = Company.objects.create(slug='acme-s', name='Acme', initials='A', industry='Retail', industry_slug='retail',
                                              location='Lusaka', owner=self.employer, description='d', email='a@x.com')
        self.admin = User.objects.create_user(username='ad', email='ad@x.com', password='OldPass123!', role=User.Role.ADMIN)
        self.url = reverse('accounts:settings')

    def test_settings_render_inside_each_dashboard(self):
        for user, marker in ((self.seeker, 'seeker-sidebar'), (self.employer, 'Post a Job'), (self.admin, 'Platform Admin')):
            self.client.force_login(user)
            html = self.client.get(self.url).content.decode()
            self.assertIn('class="sidebar', html)
            self.assertIn(marker, html)
            self.assertIn('set-tabs', html)
            self.assertNotIn('class="navbar"', html)

    def test_profile_tab_only_for_seekers_and_companies(self):
        self.client.force_login(self.admin)
        self.assertNotIn('data-tab="profile"', self.client.get(self.url).content.decode())
        self.client.force_login(self.seeker)
        self.assertIn('data-tab="profile"', self.client.get(self.url).content.decode())

    def test_account_details_can_be_updated_and_return_to_account_tab(self):
        self.client.force_login(self.seeker)
        response = self.client.post(self.url, {'action': 'profile', 'profile-username': 'sk', 'profile-first_name': 'Samuel',
                                               'profile-last_name': 'Seeker', 'profile-email': 'sk@x.com', 'profile-phone': '0977000000'})
        self.assertRedirects(response, self.url + '?tab=account')
        self.seeker.refresh_from_db()
        self.assertEqual((self.seeker.first_name, self.seeker.phone), ('Samuel', '0977000000'))

    def test_password_change_works_and_wrong_password_stays_on_security_tab(self):
        self.client.force_login(self.seeker)
        bad = self.client.post(self.url, {'action': 'password', 'password-old_password': 'nope',
                                          'password-new_password1': 'BrandNew123!x', 'password-new_password2': 'BrandNew123!x'})
        self.assertEqual(bad.status_code, 200)
        self.assertEqual(bad.context['active_tab'], 'security')
        ok = self.client.post(self.url, {'action': 'password', 'password-old_password': 'OldPass123!',
                                         'password-new_password1': 'BrandNew123!x', 'password-new_password2': 'BrandNew123!x'})
        self.assertRedirects(ok, self.url + '?tab=security')
        self.seeker.refresh_from_db()
        self.assertTrue(self.seeker.check_password('BrandNew123!x'))

    def test_company_profile_can_be_updated_from_settings(self):
        self.client.force_login(self.employer)
        form = self.client.get(self.url).context['company_form']
        data = {f'company-{n}': (form.initial.get(n) or '') for n in form.fields if n not in ('cover_image', 'clear_cover_image')}
        data.update({'action': 'company', 'company-name': 'Acme Renamed', 'company-description': 'New description',
                     'company-is_public': 'on', 'company-is_online': 'on', 'company-application_delivery_method': 'email',
                     'company-email': 'a@x.com', 'company-industry': 'Retail', 'company-location': 'Lusaka'})
        response = self.client.post(self.url, data)
        self.assertRedirects(response, self.url + '?tab=profile')
        self.company.refresh_from_db()
        self.assertEqual((self.company.name, self.company.description), ('Acme Renamed', 'New description'))

    def test_seeker_profile_can_be_updated_from_settings(self):
        self.client.force_login(self.seeker)
        form = self.client.get(self.url).context['seeker_form']
        data = {f'seeker-{n}': (form.initial.get(n) if form.initial.get(n) is not None else '') for n in form.fields
                if n not in ('cover_image', 'clear_cover_image', 'cv_file')}
        data.update({'action': 'seeker', 'seeker-headline': 'Head Baker', 'seeker-full_name': 'Sam Seeker', 'seeker-location': 'Ndola',
                     'seeker-years_experience': '4', 'seeker-experience_level': 'mid', 'seeker-availability': form.fields['availability'].choices[0][0],
                     'seeker-is_public': 'on', 'seeker-is_online': 'on'})
        response = self.client.post(self.url, data)
        if response.status_code == 200:
            self.fail(f'form errors: {response.context["seeker_form"].errors}')
        self.profile.refresh_from_db()
        self.assertEqual((self.profile.headline, self.profile.location), ('Head Baker', 'Ndola'))


class SignInAndRegisterTests(TestCase):
    def setUp(self):
        from django.core.cache import cache

        from accounts.models import User

        cache.clear()
        self.user = User.objects.create_user(username='amy', email='Amy@Example.com', password='GoodPass123!x', role=User.Role.SEEKER,
                                             first_name='Amy')
        self.login_url = reverse('accounts:login')
        self.register_url = reverse('accounts:register')

    def _seeker_payload(self, **kw):
        data = {'account_type': 'seeker', 'first_name': 'Ben', 'last_name': 'Banda', 'email': 'ben@example.com', 'phone': '0977123456',
                'password1': 'StrongPass123!', 'password2': 'StrongPass123!', 'accept_terms': 'on'}
        data.update(kw)
        return data

    # ---- sign in ----
    def test_pages_load(self):
        for url in (self.login_url, self.register_url, self.register_url + '?type=company'):
            self.assertEqual(self.client.get(url).status_code, 200)

    def test_sign_in_is_case_insensitive_and_goes_to_dashboard(self):
        response = self.client.post(self.login_url, {'email': 'amy@example.COM', 'password': 'GoodPass123!x'})
        self.assertRedirects(response, reverse('seekers:dashboard'), fetch_redirect_response=False)

    def test_wrong_password_and_unknown_email_give_the_same_message(self):
        for email, password in (('amy@example.com', 'nope'), ('ghost@example.com', 'nope')):
            response = self.client.post(self.login_url, {'email': email, 'password': password})
            self.assertContains(response, 'Invalid email or password.')

    def test_next_redirect_is_followed_but_offsite_is_rejected(self):
        ok = self.client.post(self.login_url, {'email': 'amy@example.com', 'password': 'GoodPass123!x', 'next': '/jobs/'})
        self.assertRedirects(ok, '/jobs/', fetch_redirect_response=False)
        self.client.logout()
        bad = self.client.post(self.login_url, {'email': 'amy@example.com', 'password': 'GoodPass123!x', 'next': 'https://evil.example/x'})
        self.assertNotIn('evil', bad.url)

    def test_remember_me_controls_session_length(self):
        self.client.post(self.login_url, {'email': 'amy@example.com', 'password': 'GoodPass123!x'})
        self.assertTrue(self.client.session.get_expire_at_browser_close())
        self.client.logout()
        self.client.post(self.login_url, {'email': 'amy@example.com', 'password': 'GoodPass123!x', 'remember': '1'})
        self.assertFalse(self.client.session.get_expire_at_browser_close())

    def test_repeated_failures_lock_further_attempts_even_with_the_right_password(self):
        for _ in range(5):
            self.client.post(self.login_url, {'email': 'amy@example.com', 'password': 'wrong'})
        locked = self.client.post(self.login_url, {'email': 'amy@example.com', 'password': 'GoodPass123!x'})
        self.assertEqual(locked.status_code, 429)
        self.assertContains(locked, 'Too many failed attempts', status_code=429)
        self.assertNotIn('_auth_user_id', self.client.session)

    def test_disabled_account_cannot_sign_in(self):
        self.user.is_active = False
        self.user.save()
        response = self.client.post(self.login_url, {'email': 'amy@example.com', 'password': 'GoodPass123!x'})
        self.assertContains(response, 'disabled')

    # ---- register ----
    def test_seeker_registration_creates_account_profile_and_signs_in(self):
        from accounts.models import User
        from seekers.models import SeekerProfile

        response = self.client.post(self.register_url, self._seeker_payload())
        self.assertRedirects(response, reverse('seekers:edit_profile'), fetch_redirect_response=False)
        user = User.objects.get(email='ben@example.com')
        self.assertEqual(user.role, User.Role.SEEKER)
        self.assertTrue(SeekerProfile.objects.filter(user=user).exists())
        self.assertEqual(int(self.client.session['_auth_user_id']), user.pk)

    def test_company_registration_creates_company_and_goes_to_employer_dashboard(self):
        from companies.models import Company

        response = self.client.post(self.register_url, {
            'account_type': 'company', 'company_name': 'Zed Traders', 'contact_person': 'Zed Boss', 'email': 'hr@zed.com', 'phone': '0211000000',
            'password1': 'StrongPass123!', 'password2': 'StrongPass123!', 'accept_terms': 'on'})
        self.assertRedirects(response, reverse('employers:edit_company'), fetch_redirect_response=False)
        self.assertTrue(Company.objects.filter(name='Zed Traders', owner__email='hr@zed.com').exists())

    def test_registration_validation_errors(self):
        dup = self.client.post(self.register_url, self._seeker_payload(email='AMY@example.com'))
        self.assertContains(dup, 'already exists')
        mismatch = self.client.post(self.register_url, self._seeker_payload(password2='Different123!'))
        self.assertContains(mismatch, 'do not match')
        weak = self.client.post(self.register_url, self._seeker_payload(password1='12345678', password2='12345678'))
        self.assertEqual(weak.status_code, 200)
        no_terms = self.client.post(self.register_url, self._seeker_payload(accept_terms=''))
        self.assertContains(no_terms, 'Please accept the Terms of Service')
        from accounts.models import User
        self.assertFalse(User.objects.filter(email='ben@example.com').exists())

    def test_next_is_carried_from_sign_in_through_registration(self):
        page = self.client.get(self.register_url + '?next=/jobs/some-job/').content.decode()
        self.assertIn('name="next" value="/jobs/some-job/"', page)
        response = self.client.post(self.register_url, self._seeker_payload(next='/jobs/'))
        self.assertRedirects(response, '/jobs/', fetch_redirect_response=False)

    def test_register_header_markup_and_labels(self):
        html = self.client.get(self.register_url).content.decode()
        self.assertIn('class="reg-header"', html)
        self.assertIn('<label for="id_email">', html)
        self.assertIn('autocomplete="new-password"', html)


class NotificationCentreTests(TestCase):
    def setUp(self):
        from accounts.models import User
        from companies.models import Company
        from jobs.models import Job, JobApplication, JobCategory
        from seekers.models import SeekerProfile

        self.seeker = User.objects.create_user(username='ns', email='ns@x.com', password='pw12345!', role=User.Role.SEEKER, first_name='Sam')
        self.employer = User.objects.create_user(username='ne', email='ne@x.com', password='pw12345!', role=User.Role.COMPANY, first_name='Eve')
        self.admin = User.objects.create_user(username='na', email='na@x.com', password='pw12345!', role=User.Role.ADMIN)
        self.other = User.objects.create_user(username='no', email='no@x.com', password='pw12345!', role=User.Role.SEEKER)
        SeekerProfile.objects.create(user=self.seeker, slug='sam', full_name='Sam S', headline='Baker', category='x', category_slug='x', location='L', is_public=True)
        Company.objects.create(slug='nc', name='NC', initials='N', industry='x', industry_slug='x', location='L', owner=self.employer)
        cat = JobCategory.objects.create(name='Notif Cat', slug='notif-cat')
        self.job = Job.objects.create(title='Cashier', slug='cashier-n', company_name='NC', company_slug='nc', company_initials='N', location='L',
                                      category=cat, description='d', requirements='r', posted_by=self.employer, is_active=True)
        self.JobApplication = JobApplication
        self.url = reverse('accounts:notifications')

    def _notes(self, user, **kw):
        from accounts.models import UserNotification
        return UserNotification.objects.filter(recipient=user, **kw)

    def test_registration_creates_welcome_and_alerts_admins(self):
        from accounts.models import User

        User.objects.create_user(username='fresh', email='fresh@x.com', password='pw12345!', role=User.Role.SEEKER)
        fresh = User.objects.get(username='fresh')
        self.assertTrue(self._notes(fresh, title='Welcome to PEZAJOB').exists())
        self.assertTrue(self._notes(self.admin, notification_type='new_user').exists())

    def test_application_notifies_employer_and_seeker_and_status_change_notifies_seeker(self):
        from ats.services import change_application_status

        from jobs.notifications import notify_employer_new_application

        app = self.JobApplication.objects.create(job=self.job, applicant=self.seeker, cover_letter='hi')
        notify_employer_new_application(app)
        self.assertTrue(self._notes(self.employer, title='New application: Cashier').exists())
        self.assertTrue(self._notes(self.seeker, title='Application submitted').exists())
        change_application_status(app, 'rejected', self.employer)
        self.assertTrue(self._notes(self.seeker, notification_type='application_status').exists())

    def test_every_role_can_open_the_centre_inside_its_dashboard(self):
        for user, marker in ((self.seeker, 'seeker-sidebar'), (self.employer, 'Post a Job'), (self.admin, 'Platform Admin')):
            self.client.force_login(user)
            response = self.client.get(self.url)
            self.assertEqual(response.status_code, 200)
            self.assertContains(response, 'nt-tabs')
            self.assertContains(response, marker)

    def test_viewing_does_not_mark_read_but_opening_does(self):
        from accounts.notification_service import create_user_notification

        create_user_notification(recipient=self.seeker, title='Hello', body='Body', notification_type='general', path='/jobs/')
        self.client.force_login(self.seeker)
        self.client.get(self.url)
        note = self._notes(self.seeker, title='Hello').get()
        self.assertFalse(note.is_read)
        response = self.client.get(reverse('accounts:notification_open', args=[note.pk]))
        self.assertRedirects(response, '/jobs/', fetch_redirect_response=False)
        note.refresh_from_db()
        self.assertTrue(note.is_read)

    def test_mark_all_toggle_delete_and_ownership(self):
        from accounts.notification_service import create_user_notification

        create_user_notification(recipient=self.seeker, title='A', body='a', notification_type='general')
        create_user_notification(recipient=self.other, title='B', body='b', notification_type='general')
        mine, theirs = self._notes(self.seeker, title='A').get(), self._notes(self.other, title='B').get()
        self.client.force_login(self.seeker)
        self.assertEqual(self.client.post(reverse('accounts:notification_delete', args=[theirs.pk])).status_code, 404)
        self.client.post(reverse('accounts:notification_toggle', args=[mine.pk]))
        mine.refresh_from_db()
        self.assertTrue(mine.is_read)
        self.client.post(reverse('accounts:notification_toggle', args=[mine.pk]))
        self.client.post(reverse('accounts:notifications_read_all'))
        self.assertEqual(self._notes(self.seeker, is_read=False).count(), 0)
        self.client.post(reverse('accounts:notification_delete', args=[mine.pk]))
        self.assertFalse(self._notes(self.seeker, title='A').exists())

    def test_filters_and_unread_badge_in_header(self):
        from accounts.notification_service import create_user_notification

        create_user_notification(recipient=self.seeker, title='Chat', body='hey', notification_type='message')
        create_user_notification(recipient=self.seeker, title='Alert', body='job', notification_type='job_alert')
        self.client.force_login(self.seeker)
        html = self.client.get(self.url, {'tab': 'messages'}).content.decode()
        self.assertIn('Chat', html)
        self.assertNotIn('>Alert<', html)
        self.assertIn('nav-bell-badge', self.client.get(reverse('jobs:list')).content.decode())

    def test_notification_failure_never_breaks_the_action(self):
        from unittest import mock

        with mock.patch('accounts.events.create_user_notification', side_effect=RuntimeError('boom')):
            self.JobApplication.objects.create(job=self.job, applicant=self.seeker, cover_letter='hi')

    def test_notification_pagination_has_page_numbers_and_keeps_filters(self):
        from accounts.notification_service import create_user_notification

        for i in range(45):
            create_user_notification(recipient=self.seeker, title=f'N{i}', body='b', notification_type='general')
        self.client.force_login(self.seeker)
        response = self.client.get(self.url, {'tab': 'all'})
        html = response.content.decode()
        self.assertEqual(len(response.context['notifications']), 20)
        self.assertIn('pj-page-num', html)
        self.assertIn('?page=3&amp;tab=all', html)
        self.assertEqual(len(self.client.get(self.url, {'page': 3}).context['notifications']), 6)
