"""Account tests.  Run:  venv/bin/python manage.py test apps.accounts"""
import re
import shutil
import tempfile
from decimal import Decimal

from django.contrib.auth.models import User
from django.core import mail
from django.core.cache import cache
from django.core.files.uploadedfile import SimpleUploadedFile
from django.test import TestCase, override_settings

from apps.store import magazine as mag
from apps.store.models import DigitalAccess, Order, Product, private_storage

TMP = tempfile.mkdtemp()
GIF = (b'GIF89a\x01\x00\x01\x00\x80\x00\x00\x00\x00\x00\xff\xff\xff!\xf9\x04\x00\x00\x00\x00\x00,'
       b'\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x02D\x01\x00;')


def make_pdf(pages):
    import io
    from pypdf import PdfWriter
    w = PdfWriter()
    for _ in range(pages):
        w.add_blank_page(width=300, height=400)
    buf = io.BytesIO()
    w.write(buf)
    return buf.getvalue()


@override_settings(MEDIA_ROOT=TMP, TELEGRAM_BOT_TOKEN='', ALLOWED_HOSTS=['testserver'],
                   EMAIL_BACKEND='django.core.mail.backends.locmem.EmailBackend')
class AccountTests(TestCase):
    @classmethod
    def setUpClass(cls):
        super().setUpClass()
        private_storage.location = TMP

    @classmethod
    def tearDownClass(cls):
        super().tearDownClass()
        shutil.rmtree(TMP, ignore_errors=True)

    def setUp(self):
        cache.clear()

    def signup(self, **kw):
        data = {'name': 'Rahim Uddin', 'email': 'rahim@example.com', 'phone': '01711111111',
                'password1': 'History-1971-Dhaka', 'password2': 'History-1971-Dhaka', 'wants_newsletter': 'on',
                'accept_terms': 'on'}
        data.update(kw)
        return self.client.post('/account/signup/', data)

    def test_signup_creates_profile_and_sends_verification(self):
        r = self.signup()
        self.assertEqual(r.status_code, 302)
        u = User.objects.get(email='rahim@example.com')
        self.assertEqual(u.profile.phone, '01711111111')
        self.assertTrue(u.profile.wants_newsletter)
        self.assertEqual(len(mail.outbox), 1)
        link = re.search(r'http://testserver(/account/verify/[^"\s]+)', mail.outbox[0].body).group(1)
        self.client.get(link)
        u.profile.refresh_from_db()
        self.assertTrue(u.profile.email_verified)
        from apps.newsletter.models import NewsletterSubscriber
        self.assertTrue(NewsletterSubscriber.objects.get(email='rahim@example.com').is_confirmed)

    def test_duplicates_and_weak_password_rejected(self):
        self.signup()
        self.client.post('/account/logout/')
        self.assertContains(self.signup(phone=''), 'already exists')
        self.assertContains(self.signup(email='x@example.com'), 'already uses this mobile')
        self.assertContains(self.signup(email='y@example.com', phone='', password1='12345678', password2='12345678'),
                            'acct-err')
        self.assertEqual(User.objects.count(), 1)

    def test_sign_in_with_email_or_phone(self):
        self.signup()
        self.client.post('/account/logout/')
        r = self.client.post('/account/login/', {'accept_terms': 'on', 'identifier': 'RAHIM@example.com', 'password': 'History-1971-Dhaka'})
        self.assertRedirects(r, '/account/')
        self.client.post('/account/logout/')
        r = self.client.post('/account/login/', {'accept_terms': 'on', 'identifier': '+880 1711-111111', 'password': 'History-1971-Dhaka'})
        self.assertRedirects(r, '/account/')

    def test_wrong_passwords_get_locked(self):
        self.signup()
        self.client.post('/account/logout/')
        for _ in range(5):
            self.client.post('/account/login/', {'accept_terms': 'on', 'identifier': 'rahim@example.com', 'password': 'nope'})
        r = self.client.post('/account/login/', {'accept_terms': 'on', 'identifier': 'rahim@example.com', 'password': 'History-1971-Dhaka'})
        self.assertContains(r, 'Too many wrong attempts')

    def test_next_redirect_is_safe(self):
        self.signup()
        self.client.post('/account/logout/')
        r = self.client.post('/account/login/', {'accept_terms': 'on', 'identifier': 'rahim@example.com', 'password': 'History-1971-Dhaka',
                                                 'next': 'https://evil.example/steal'})
        self.assertRedirects(r, '/account/')
        self.client.post('/account/logout/')
        r = self.client.post('/account/login/', {'accept_terms': 'on', 'identifier': 'rahim@example.com', 'password': 'History-1971-Dhaka',
                                                 'next': '/shop/'})
        self.assertEqual(r['Location'], '/shop/')

    def test_account_pages_need_sign_in(self):
        r = self.client.get('/account/')
        self.assertEqual(r.status_code, 302)
        self.assertTrue(r['Location'].startswith('/account/login/'))

    def test_password_reset_email(self):
        self.signup()
        self.client.post('/account/logout/')
        mail.outbox.clear()
        self.client.post('/account/password-reset/', {'email': 'rahim@example.com'})
        self.assertEqual(len(mail.outbox), 1)
        self.assertIn('/account/password-reset/', mail.outbox[0].body)

    def test_orders_and_magazines_follow_the_account(self):
        issue = Product.objects.create(
            product_type='magazine', title='Mughal Issue', description='x', price=Decimal('350'),
            digital_price=Decimal('150'), preview_pages='1-2',
            image=SimpleUploadedFile('c.gif', GIF, content_type='image/gif'),
            pdf_file=SimpleUploadedFile('m.pdf', make_pdf(5), content_type='application/pdf'))
        self.signup()
        self.client.post(f'/magazine/{issue.slug}/request-access/',
                         {'name': 'Rahim', 'phone': '01711111111', 'trx_id': 'AB12CD34EF', 'accept_terms': 'true'})
        access = DigitalAccess.objects.get()
        self.assertEqual(access.user.email, 'rahim@example.com')
        mag.approve(access)

        # A different device: sign in → full magazine without any cookie
        other = self.client_class()
        other.post('/account/login/', {'accept_terms': 'on', 'identifier': '01711111111', 'password': 'History-1971-Dhaka'})
        self.assertEqual(other.get(f'/magazine/{issue.slug}/read/full.pdf').status_code, 200)
        page = other.get('/account/')
        self.assertContains(page, 'Mughal Issue')
        self.assertContains(page, 'Magazine reader')

        import json
        tee = Product.objects.create(title='Tee', description='x', price=Decimal('600'),
                                     image=SimpleUploadedFile('t.gif', GIF, content_type='image/gif'))
        other.post('/shop/place-order/', json.dumps({'name': 'Rahim', 'phone': '01711111111', 'location': 'Dhaka',
                                                     'items': [{'product_id': tee.id, 'quantity': 1}], 'accept_terms': True}),
                   content_type='application/json')
        self.assertEqual(Order.objects.get().user.email, 'rahim@example.com')
        self.assertContains(other.get('/account/'), 'Shop customer')

    def test_claim_earlier_purchase_needs_phone_and_code(self):
        guest_order = Order.objects.create(name='R', phone='01711111111', location='Dhaka', total_amount=600)
        self.signup()
        self.client.post('/account/claim/', {'phone': '01700000000', 'code': str(guest_order.pk)})
        guest_order.refresh_from_db()
        self.assertIsNone(guest_order.user)
        self.client.post('/account/claim/', {'phone': '01711111111', 'code': str(guest_order.pk)})
        guest_order.refresh_from_db()
        self.assertEqual(guest_order.user.email, 'rahim@example.com')

    def test_admin_customer_list(self):
        self.signup()
        User.objects.create_superuser('boss', 'b@example.com', 'pw-123456789')
        self.client.login(username='boss', password='pw-123456789')
        r = self.client.get('/admin/accounts/customerprofile/?type=none')
        self.assertContains(r, 'rahim@example.com')


@override_settings(GOOGLE_CLIENT_ID='test-client.apps.googleusercontent.com', ALLOWED_HOSTS=['testserver'],
                   EMAIL_BACKEND='django.core.mail.backends.locmem.EmailBackend', TELEGRAM_BOT_TOKEN='')
class GoogleSignInTests(TestCase):
    CLAIMS = {'iss': 'https://accounts.google.com', 'sub': '1234567890', 'email': 'Karim@Gmail.com',
              'email_verified': True, 'given_name': 'Karim', 'family_name': 'Ahmed'}

    def post(self, claims=None, next_url=''):
        import json
        from unittest import mock
        with mock.patch('apps.accounts.views.verify_google_credential', return_value=claims):
            return self.client.post('/account/google/', json.dumps({'credential': 'x', 'next': next_url, 'accept_terms': True}),
                                    content_type='application/json')

    def test_new_reader_gets_an_account(self):
        r = self.post(self.CLAIMS)
        self.assertEqual(r.json(), {'ok': True, 'redirect': '/account/'})
        u = User.objects.get(email='karim@gmail.com')
        self.assertEqual((u.first_name, u.profile.google_sub, u.profile.email_verified), ('Karim', '1234567890', True))
        self.assertFalse(u.has_usable_password())
        self.assertContains(self.client.get('/account/'), 'Hello, Karim')

    def test_old_account_without_profile_can_use_google(self):
        # Accounts made before profiles existed (like the first admin) must not crash Google sign-in,
        # and an admin keeps their password.
        from apps.accounts.models import CustomerProfile
        admin = User.objects.create_superuser('sazzad', 'karim@gmail.com', 'Admin-Pass-1971')
        CustomerProfile.objects.filter(user=admin).delete()
        r = self.post(self.CLAIMS)
        self.assertEqual(r.status_code, 200)
        self.assertTrue(r.json()['ok'])
        admin.refresh_from_db()
        self.assertEqual(admin.profile.google_sub, '1234567890')
        self.assertTrue(admin.check_password('Admin-Pass-1971'))

    def test_backfill_migration_gives_every_account_a_profile(self):
        from importlib import import_module
        from django.apps import apps as global_apps
        from apps.accounts.models import CustomerProfile
        u = User.objects.create_user('old', 'old@example.com', 'x-12345678')
        CustomerProfile.objects.filter(user=u).delete()
        import_module('apps.accounts.migrations.0005_backfill_profiles').backfill(global_apps, None)
        self.assertTrue(CustomerProfile.objects.filter(user=u).exists())

    def test_returning_reader_uses_same_account(self):
        self.post(self.CLAIMS)
        self.client.post('/account/logout/')
        self.post(dict(self.CLAIMS, email='karim.new@gmail.com'))  # Google email changed, same Google ID
        self.assertEqual(User.objects.count(), 1)

    def test_links_to_existing_verified_email_account(self):
        u = User.objects.create_user('karim@gmail.com', 'karim@gmail.com', 'History-1971-Dhaka')
        u.profile.email_verified = True
        u.profile.save()
        self.post(self.CLAIMS)
        u.refresh_from_db()
        self.assertEqual(User.objects.count(), 1)
        self.assertEqual(u.profile.google_sub, '1234567890')
        self.assertTrue(u.check_password('History-1971-Dhaka'))  # their password keeps working

    def test_unconfirmed_squatter_password_is_removed(self):
        u = User.objects.create_user('karim@gmail.com', 'karim@gmail.com', 'Squatter-Pass-99')
        self.post(self.CLAIMS)
        u.refresh_from_db()
        self.assertFalse(u.has_usable_password())

    def test_bad_token_and_unsafe_next_and_missing_setup(self):
        r = self.post(None)
        self.assertEqual(r.status_code, 400)
        self.assertEqual(User.objects.count(), 0)
        r = self.post(self.CLAIMS, next_url='https://evil.example/')
        self.assertEqual(r.json()['redirect'], '/account/')
        with self.settings(GOOGLE_CLIENT_ID=''):
            self.assertEqual(self.post(self.CLAIMS).status_code, 503)

    def test_button_only_when_configured(self):
        self.assertContains(self.client.get('/account/login/'), 'accounts.google.com/gsi/client')
        with self.settings(GOOGLE_CLIENT_ID=''):
            self.assertNotContains(self.client.get('/account/login/'), 'gsi/client')

    def test_google_user_can_set_a_password(self):
        self.post(self.CLAIMS)
        self.assertContains(self.client.get('/account/settings/'), 'Set a password')
        self.client.post('/account/settings/', {'form': 'password', 'new_password1': 'Lalbagh-Fort-1678',
                                                'new_password2': 'Lalbagh-Fort-1678'})
        self.assertTrue(User.objects.get().check_password('Lalbagh-Fort-1678'))


@override_settings(ALLOWED_HOSTS=['testserver'], TELEGRAM_BOT_TOKEN='')
class AddressAndOrderTrackingTests(TestCase):
    def setUp(self):
        from decimal import Decimal
        from django.core.files.uploadedfile import SimpleUploadedFile
        from apps.store.models import Product
        gif = (b'GIF89a\x01\x00\x01\x00\x80\x00\x00\x00\x00\x00\xff\xff\xff!\xf9\x04\x00\x00\x00\x00\x00,'
               b'\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x02D\x01\x00;')
        self.tee = Product.objects.create(product_type='merch', title='Tee', slug='tee-x', description='x',
                                          price=Decimal('500'), image=SimpleUploadedFile('t.gif', gif, content_type='image/gif'))
        self.user = User.objects.create_user('nadia', 'nadia@example.com', 'pw-12345678', first_name='Nadia')
        self.client.login(username='nadia', password='pw-12345678')

    def order(self, **extra):
        import json
        data = {'name': 'Nadia', 'phone': '01711111111', 'items': [{'product_id': self.tee.pk, 'quantity': 1}],
                'accept_terms': True}
        data.update(extra)
        return self.client.post('/shop/place-order/', json.dumps(data), content_type='application/json').json()

    def test_location_data(self):
        from apps.accounts import locations
        self.assertEqual(len(locations.divisions()), 8)
        self.assertTrue(locations.is_valid('Dhaka', 'Dhaka', 'Dhanmondi (Dhaka City)'))
        self.assertTrue(locations.is_valid('Chattogram', "Cox's Bazar", 'Teknaf'))
        self.assertFalse(locations.is_valid('Dhaka', 'Sylhet', 'Sylhet Sadar'))

    def test_profile_saves_address(self):
        r = self.client.post('/account/settings/', {'form': 'profile', 'name': 'Nadia Islam', 'phone': '01711111111',
                                                    'alt_phone': '01822222222', 'division': 'Sylhet', 'district': 'Sylhet',
                                                    'area': 'Sylhet City Corporation', 'address': 'House 4, Zindabazar'})
        self.assertEqual(r.status_code, 302)
        p = self.user.profile
        p.refresh_from_db()
        self.assertEqual(p.full_address, 'House 4, Zindabazar, Sylhet City Corporation, Sylhet, Sylhet')
        bad = self.client.post('/account/settings/', {'form': 'profile', 'name': 'N', 'division': 'Dhaka',
                                                      'district': 'Sylhet', 'area': 'Sylhet Sadar'})
        self.assertContains(bad, 'choose your division')
        page = self.client.get('/shop/').content.decode()
        self.assertIn('data-value="Sylhet City Corporation"', page)  # checkout is pre-filled

    def test_order_with_address_and_tracking(self):
        from apps.store.models import Order
        bad = self.order(division='Dhaka', district='Dhaka', area='Nowhere', address_line='x')
        self.assertFalse(bad['success'])
        r = self.order(division='Dhaka', district='Dhaka', area='Mirpur (Dhaka City)', address_line='Road 2, House 9',
                       save_address=True)
        self.assertTrue(r['success'])
        o = Order.objects.get()
        self.assertEqual(o.location, 'Road 2, House 9, Mirpur (Dhaka City), Dhaka, Dhaka')
        self.assertEqual(r['track_url'], f'/account/orders/{o.pk}/')
        self.user.profile.refresh_from_db()
        self.assertEqual(self.user.profile.area, 'Mirpur (Dhaka City)')
        page = self.client.get(r['track_url'])
        self.assertContains(page, 'অর্ডার পেয়েছি')
        o.status, o.courier, o.tracking_code = 'shipped', 'Pathao', 'PX123'
        o.save()
        o.refresh_from_db()
        self.assertIsNotNone(o.confirmed_at)
        page = self.client.get(r['track_url'])
        self.assertContains(page, 'আপনার অর্ডার পথে আছে')
        self.assertContains(page, 'PX123')
        other = User.objects.create_user('z', 'z@example.com', 'pw-12345678')
        self.client.force_login(other)
        self.assertEqual(self.client.get(r['track_url']).status_code, 404)
