"""Student-verification (moderated Verified-Student badge) tests."""

import os
import unittest

os.environ["TEST_DATABASE_URL"] = "sqlite:///:memory:"

from werkzeug.security import generate_password_hash

from app import create_app
from app.auth import verification as vsvc
from app.extensions import db
from app.models import (
    Notification,
    StudentVerificationStatus,
    User,
    UserProfile,
    UserRole,
)

S = StudentVerificationStatus


class StudentVerificationTests(unittest.TestCase):
    def setUp(self):
        self.app = create_app("testing")
        self.ctx = self.app.app_context()
        self.ctx.push()
        db.create_all()

    def tearDown(self):
        db.session.remove()
        db.drop_all()
        self.ctx.pop()

    def _user(self, email="s@c.edu", role=UserRole.STUDENT, name="Student One"):
        u = User(email=email, password_hash=generate_password_hash("Passw0rd!"),
                 is_verified=True, role=role)
        u.profile = UserProfile(full_name=name)
        db.session.add(u)
        db.session.commit()
        return u

    def _admin(self):
        return self._user("admin@c.edu", role=UserRole.SUPER_ADMIN, name="Admin")

    def _notifs(self, user):
        return db.session.scalars(
            db.select(Notification).where(Notification.user_id == user.id)
        ).all()

    def _submit(self, u, mobile="+91 9876543210", uni="Sharda University", sid="2021001234"):
        return vsvc.submit_verification(u, mobile, uni, sid)

    # --- submission ------------------------------------------------------------
    def test_submit_succeeds(self):
        u = self._user()
        self._submit(u)
        self.assertEqual(u.profile.verification_status, S.PENDING)
        self.assertEqual(u.profile.university_name, "Sharda University")
        self.assertEqual(u.profile.university_student_id, "2021001234")
        self.assertEqual(u.profile.phone_number, "+91 9876543210")  # mobile reuses phone
        self.assertIsNotNone(u.profile.verification_submitted_at)
        self.assertFalse(u.is_student_verified)                     # pending ≠ verified
        self.assertTrue(any("submitted" in n.message.lower() for n in self._notifs(u)))

    def test_missing_fields_fail(self):
        u = self._user()
        for args in [("", "Sharda", "123"), ("9876543210", "", "123"), ("9876543210", "Sharda", "")]:
            with self.assertRaises(vsvc.VerificationError):
                vsvc.submit_verification(u, *args)
        self.assertEqual(u.profile.verification_status, S.NOT_VERIFIED)

    def test_invalid_mobile_rejected(self):
        u = self._user()
        with self.assertRaises(vsvc.VerificationError):
            vsvc.submit_verification(u, "not-a-number", "Sharda", "123")

    def test_whitespace_is_trimmed(self):
        u = self._user()
        self._submit(u, mobile="  9876543210  ", uni="  Sharda  ", sid="  ID9  ")
        self.assertEqual(u.profile.university_name, "Sharda")
        self.assertEqual(u.profile.university_student_id, "ID9")

    def test_duplicate_pending_blocked(self):
        u = self._user()
        self._submit(u)
        with self.assertRaises(vsvc.VerificationError):
            self._submit(u)

    def test_verified_submit_update_goes_to_pending(self):
        # A verified student submitting updated info drops to PENDING (badge off).
        u, admin = self._user(), self._admin()
        self._submit(u)
        vsvc.approve_verification(u, admin)
        self.assertTrue(u.is_student_verified)
        vsvc.submit_verification(u, "9998887777", "New University", "NEW-42")
        self.assertEqual(u.profile.verification_status, S.PENDING)
        self.assertFalse(u.is_student_verified)
        self.assertEqual(u.profile.university_student_id, "NEW-42")

    # --- badge logic -----------------------------------------------------------
    def test_email_verified_alone_does_not_grant_badge(self):
        u = self._user()                       # is_verified (email) = True
        self.assertTrue(u.is_verified)
        self.assertFalse(u.is_student_verified)  # badge requires admin approval

    def test_pending_and_rejected_do_not_grant_badge(self):
        u, admin = self._user(), self._admin()
        self._submit(u)
        self.assertFalse(u.is_student_verified)   # pending
        vsvc.reject_verification(u, admin)
        self.assertFalse(u.is_student_verified)   # rejected

    # --- admin approve / reject -----------------------------------------------
    def test_approve_grants_badge_and_records_admin(self):
        u, admin = self._user(), self._admin()
        self._submit(u)
        vsvc.approve_verification(u, admin)
        self.assertEqual(u.profile.verification_status, S.VERIFIED)
        self.assertTrue(u.is_student_verified)
        self.assertEqual(u.profile.verification_reviewed_by, admin.id)
        self.assertIsNotNone(u.profile.verification_reviewed_at)
        self.assertTrue(any("congratulations" in n.message.lower() for n in self._notifs(u)))

    def test_reject_keeps_badge_off_and_allows_resubmit(self):
        u, admin = self._user(), self._admin()
        self._submit(u)
        vsvc.reject_verification(u, admin, reason="ID doesn't match records")
        self.assertEqual(u.profile.verification_status, S.REJECTED)
        self.assertFalse(u.is_student_verified)
        self.assertEqual(u.profile.verification_reason, "ID doesn't match records")
        self.assertTrue(any("not approved" in n.message.lower() for n in self._notifs(u)))
        # can update + resubmit
        self._submit(u, sid="NEW-ID")
        self.assertEqual(u.profile.verification_status, S.PENDING)

    def test_approve_requires_pending(self):
        u, admin = self._user(), self._admin()
        with self.assertRaises(vsvc.VerificationError):
            vsvc.approve_verification(u, admin)      # nothing submitted

    def test_pending_queue(self):
        u, admin = self._user(), self._admin()
        self._submit(u)
        pending = vsvc.get_pending_verifications()
        self.assertIn(u.id, [p.id for p in pending])
        self.assertEqual(vsvc.get_pending_count(), 1)

    # --- routes: authorization -------------------------------------------------
    def test_non_admin_cannot_approve(self):
        u, admin = self._user(), self._admin()
        self._submit(u)
        client = self.app.test_client()
        client.post("/login", data={"email": "s@c.edu", "password": "Passw0rd!"})
        resp = client.post(f"/admin/verifications/{u.public_id}/approve", data={})
        self.assertEqual(resp.status_code, 403)
        self.assertFalse(db.session.get(User, u.id).is_student_verified)

    def test_admin_route_approve_grants_badge(self):
        u, admin = self._user(), self._admin()
        self._submit(u)
        client = self.app.test_client()
        client.post("/login", data={"email": "admin@c.edu", "password": "Passw0rd!"})
        resp = client.post(f"/admin/verifications/{u.public_id}/approve", data={})
        self.assertEqual(resp.status_code, 302)
        self.assertTrue(db.session.get(User, u.id).is_student_verified)

    def test_admin_route_reject(self):
        u, admin = self._user(), self._admin()
        self._submit(u)
        client = self.app.test_client()
        client.post("/login", data={"email": "admin@c.edu", "password": "Passw0rd!"})
        resp = client.post(f"/admin/verifications/{u.public_id}/reject",
                           data={"reason": "Blurry ID"})
        self.assertEqual(resp.status_code, 302)
        reloaded = db.session.get(User, u.id)
        self.assertEqual(reloaded.profile.verification_status, S.REJECTED)
        self.assertEqual(reloaded.profile.verification_reason, "Blurry ID")

    # --- draft edit mode: badge stays until the update is submitted -----------
    def _verify(self, u, admin):
        self._submit(u)
        vsvc.approve_verification(u, admin)

    def test_entering_edit_mode_keeps_badge_and_values_and_status(self):
        u, admin = self._user(), self._admin()
        self._verify(u, admin)
        client = self.app.test_client()
        client.post("/login", data={"email": "s@c.edu", "password": "Passw0rd!"})
        html = client.get("/profile?verify_edit=1").get_data(as_text=True)

        reloaded = db.session.get(User, u.id)
        self.assertEqual(reloaded.profile.verification_status, S.VERIFIED)   # status untouched
        self.assertTrue(reloaded.is_student_verified)                       # badge kept
        self.assertEqual(reloaded.profile.university_name, "Sharda University")  # values untouched
        self.assertIn("Submit Updated Verification", html)                  # edit form shown
        self.assertIn("badge will remain", html)                            # info alert
        self.assertIn('value="+91 9876543210"', html)                       # pre-filled

    def test_cancel_leaves_verified_unchanged(self):
        u, admin = self._user(), self._admin()
        self._verify(u, admin)
        client = self.app.test_client()
        client.post("/login", data={"email": "s@c.edu", "password": "Passw0rd!"})
        # Cancel is just a link back to /profile — read-only, still verified.
        html = client.get("/profile").get_data(as_text=True)
        self.assertNotIn("Submit Updated Verification", html)
        self.assertTrue(db.session.get(User, u.id).is_student_verified)

    def test_no_notification_on_entering_edit_mode_only_on_submit(self):
        u, admin = self._user(), self._admin()
        self._verify(u, admin)
        before = len(self._notifs(u))
        client = self.app.test_client()
        client.post("/login", data={"email": "s@c.edu", "password": "Passw0rd!"})
        client.get("/profile?verify_edit=1")                     # entering edit mode
        self.assertEqual(len(self._notifs(u)), before)           # no notification

        client.post("/profile", data={
            "submit_verification": "1", "mobile_number": "9876543210",
            "university_name": "Sharda", "university_student_id": "UPD-1"})
        reloaded = db.session.get(User, u.id)
        self.assertEqual(reloaded.profile.verification_status, S.PENDING)   # badge gone
        self.assertFalse(reloaded.is_student_verified)
        self.assertEqual(len(self._notifs(u)), before + 1)                 # notified on submit

    def test_update_submission_then_admin_approval_restores_badge(self):
        u, admin = self._user(), self._admin()
        self._verify(u, admin)
        vsvc.submit_verification(u, u.profile.phone_number, u.profile.university_name, "NEW-ID")
        self.assertEqual(u.profile.verification_status, S.PENDING)
        self.assertFalse(u.is_student_verified)
        vsvc.approve_verification(u, admin)
        self.assertTrue(u.is_student_verified)
        self.assertEqual(u.profile.university_student_id, "NEW-ID")

    # --- data integrity: verified phone cannot be edited via Edit Profile ------
    def test_edit_profile_cannot_change_verified_phone(self):
        u, admin = self._user(), self._admin()
        self._submit(u)
        vsvc.approve_verification(u, admin)
        client = self.app.test_client()
        client.post("/login", data={"email": "s@c.edu", "password": "Passw0rd!"})
        # Crafted request including a phone_number field must be ignored server-side.
        client.post("/profile", data={
            "save_profile": "1", "full_name": "Renamed Student", "bio": "hi",
            "preferred_meeting_location": "Library", "phone_number": "0000000000"})
        reloaded = db.session.get(User, u.id)
        self.assertEqual(reloaded.profile.phone_number, "+91 9876543210")  # unchanged
        self.assertEqual(reloaded.profile.full_name, "Renamed Student")    # normal field updated
        self.assertTrue(reloaded.is_student_verified)                      # still verified

    # --- route: profile submission --------------------------------------------
    def test_profile_route_submission(self):
        u = self._user()
        client = self.app.test_client()
        client.post("/login", data={"email": "s@c.edu", "password": "Passw0rd!"})
        resp = client.post("/profile", data={
            "submit_verification": "1", "mobile_number": "9876543210",
            "university_name": "Sharda University", "university_student_id": "2021001234"})
        self.assertEqual(resp.status_code, 302)
        self.assertEqual(db.session.get(User, u.id).profile.verification_status, S.PENDING)


if __name__ == "__main__":
    unittest.main()
