import unittest

from fastapi import HTTPException

from app.modules.auth.application.auth.password_hasher import (
    hash_password,
    validate_password_strength,
    verify_password,
)
from app.modules.auth.application.auth_service import AuthService


class TestOwnerAuth(unittest.TestCase):
    def setUp(self):
        self.auth_service = AuthService()

    def test_password_hasher_pbkdf2(self):
        pwd = "DSCons@2026#SecurePassword"
        h = hash_password(pwd)
        self.assertTrue(h.startswith("pbkdf2_sha256$100000$"))
        self.assertTrue(verify_password(pwd, h))
        self.assertFalse(verify_password("wrong_pwd", h))
        self.assertFalse(verify_password("", h))
        self.assertFalse(verify_password(pwd, None))

    def test_password_strength_validation(self):
        # Too short
        valid, msg = validate_password_strength("short")
        self.assertFalse(valid)
        self.assertIn("tối thiểu 8", msg)

        # Valid strong password
        valid, msg = validate_password_strength("DSCons@2026")
        self.assertTrue(valid)
        self.assertIsNone(msg)

    def test_new_owner_email_registration(self):
        # Test dinhsonconstruction@gmail.com registration
        profile = {
            "email": "dinhsonconstruction@gmail.com",
            "sub": "google-oauth2|dinhsonconstruction-test-sub-id",
            "name": "Dinh Son Construction",
            "picture": "https://lh3.googleusercontent.com/a/test",
        }

        # Clean up if user already exists from previous runs/tests
        with self.auth_service.get_connection() as conn, conn.cursor() as cur:
            cur.execute("DELETE FROM erp_users WHERE email = %s;", (profile["email"],))
            conn.commit()

        user = self.auth_service.get_or_create_google_user(profile)
        self.assertIsNotNone(user)
        self.assertEqual(user["email"], profile["email"])
        self.assertEqual(user["role"], "superadmin")

    def test_original_owner_email_registration(self):
        # Test mirsixabcf2@gmail.com registration
        profile = {
            "email": "mirsixabcf2@gmail.com",
            "sub": "google-oauth2|mirsixabcf2-test-sub-id",
            "name": "Mir Six",
            "picture": "https://lh3.googleusercontent.com/a/test-original",
        }

        with self.auth_service.get_connection() as conn, conn.cursor() as cur:
            cur.execute("DELETE FROM erp_users WHERE email = %s;", (profile["email"],))
            conn.commit()

        user = self.auth_service.get_or_create_google_user(profile)
        self.assertIsNotNone(user)
        self.assertEqual(user["email"], profile["email"])
        self.assertEqual(user["role"], "superadmin")

    def test_unknown_email_registration_rejected_zero_trust(self):
        # Strict Zero-Trust: Arbitrary unknown email not pre-registered must be rejected with 403
        profile = {
            "email": "unauthorized_guest@gmail.com",
            "sub": "google-oauth2|unauthorized-guest-test-sub-id",
            "name": "Unauthorized Guest",
            "picture": "https://lh3.googleusercontent.com/a/test-unauthorized",
        }

        with self.auth_service.get_connection() as conn:
            with conn.cursor() as cur:
                # Ensure there is at least one admin user so system isn't fresh
                cur.execute(
                    "INSERT INTO erp_users (company_id, email, full_name, role) SELECT id, 'admin_dummy@dscons.vn', 'Dummy Admin', 'superadmin' FROM companies LIMIT 1 ON CONFLICT (email) DO NOTHING;"
                )
                cur.execute(
                    "DELETE FROM erp_users WHERE email = %s;", (profile["email"],)
                )
                conn.commit()

        with self.assertRaises(HTTPException) as ctx:
            self.auth_service.get_or_create_google_user(profile)
        self.assertEqual(ctx.exception.status_code, 403)
        self.assertIn("chưa được cấp phép truy cập", ctx.exception.detail)

        # Cleanup
        with self.auth_service.get_connection() as conn:
            with conn.cursor() as cur:
                cur.execute(
                    "DELETE FROM erp_users WHERE email IN (%s, 'admin_dummy@dscons.vn');",
                    (profile["email"],),
                )
                conn.commit()

    def test_preregistered_user_google_login(self):
        # Pre-registered user created by admin in erp_users can successfully log in with Google OAuth
        test_email = "preregistered_qs@dscons.vn"
        profile = {
            "email": test_email,
            "sub": "google-oauth2|preregistered-qs-test-sub-id",
            "name": "Pre-registered QS",
            "picture": "https://lh3.googleusercontent.com/a/test-prereg",
        }

        with self.auth_service.get_connection() as conn, conn.cursor() as cur:
            cur.execute("DELETE FROM erp_users WHERE email = %s;", (test_email,))
            cur.execute(
                "INSERT INTO erp_users (company_id, email, full_name, role, is_active) SELECT id, %s, %s, 'qs', TRUE FROM companies LIMIT 1;",
                (test_email, "Pre-registered QS"),
            )
            conn.commit()

        user = self.auth_service.get_or_create_google_user(profile)
        self.assertIsNotNone(user)
        self.assertEqual(user["email"], test_email)
        self.assertEqual(user["role"], "qs")

        # Cleanup
        with self.auth_service.get_connection() as conn, conn.cursor() as cur:
            cur.execute("DELETE FROM erp_users WHERE email = %s;", (test_email,))
            conn.commit()

    def test_inactive_user_google_login_rejected(self):
        # Inactive user must be blocked with 403
        test_email = "deactivated_user@dscons.vn"
        profile = {
            "email": test_email,
            "sub": "google-oauth2|deactivated-user-test-sub-id",
            "name": "Deactivated User",
            "picture": "https://lh3.googleusercontent.com/a/test-deact",
        }

        with self.auth_service.get_connection() as conn, conn.cursor() as cur:
            cur.execute("DELETE FROM erp_users WHERE email = %s;", (test_email,))
            cur.execute(
                "INSERT INTO erp_users (company_id, email, full_name, role, is_active) SELECT id, %s, %s, 'engineer', FALSE FROM companies LIMIT 1;",
                (test_email, "Deactivated User"),
            )
            conn.commit()

        with self.assertRaises(HTTPException) as ctx:
            self.auth_service.get_or_create_google_user(profile)
        self.assertEqual(ctx.exception.status_code, 403)
        self.assertIn("vô hiệu hóa", ctx.exception.detail)

        # Cleanup
        with self.auth_service.get_connection() as conn, conn.cursor() as cur:
            cur.execute("DELETE FROM erp_users WHERE email = %s;", (test_email,))
            conn.commit()

    def test_authenticate_email_user_owner_success(self):
        # Test direct email authentication with correct password
        result = self.auth_service.authenticate_email_user(
            email="admin@dscons.vn",
            password="DSCons@2026#SecureAdmin",
            full_name="Ban Giám Đốc DSCons",
        )
        self.assertIn("access_token", result)
        self.assertIn("user", result)
        self.assertEqual(result["user"]["email"], "admin@dscons.vn")
        self.assertEqual(result["user"]["role"], "superadmin")

    def test_authenticate_email_user_wrong_password_rejected(self):
        # Test that entering wrong password (e.g. 123456) is rejected with 401 Unauthorized
        with self.assertRaises(HTTPException) as ctx:
            self.auth_service.authenticate_email_user(
                email="admin@dscons.vn",
                password="123456",
            )
        self.assertEqual(ctx.exception.status_code, 401)
        self.assertIn("không chính xác", ctx.exception.detail)

    def test_authenticate_email_missing_password_rejected(self):
        with self.assertRaises(HTTPException) as ctx:
            self.auth_service.authenticate_email_user(
                email="admin@dscons.vn",
                password="",
            )
        self.assertEqual(ctx.exception.status_code, 400)

    def test_account_lockout_after_failed_attempts(self):
        # Test lockout protection after repeated failed logins
        test_email = "lockout_test@dscons.vn"
        pwd = "SecurePassword@2026"

        # Setup test account with known password
        with self.auth_service.get_connection() as conn, conn.cursor() as cur:
            cur.execute("DELETE FROM erp_users WHERE email = %s;", (test_email,))
            conn.commit()

        # Register user with password
        reg_result = self.auth_service.create_user_account(
            email=test_email,
            password=pwd,
            full_name="Lockout Test User",
        )
        self.assertIsNotNone(reg_result["id"])

        # Try 4 wrong passwords (should return 401)
        for i in range(4):
            with self.assertRaises(HTTPException) as ctx:
                self.auth_service.authenticate_email_user(
                    email=test_email, password="wrong_password_test"
                )
            self.assertEqual(ctx.exception.status_code, 401)

        # 5th attempt must lock the account (status 423)
        with self.assertRaises(HTTPException) as ctx:
            self.auth_service.authenticate_email_user(
                email=test_email, password="wrong_password_test"
            )
        self.assertEqual(ctx.exception.status_code, 423)
        self.assertIn("khóa", ctx.exception.detail.lower())

        # Cleanup test account
        with self.auth_service.get_connection() as conn, conn.cursor() as cur:
            cur.execute("DELETE FROM erp_users WHERE email = %s;", (test_email,))
            conn.commit()

    def test_change_password_flow(self):
        test_email = "pwd_change_test@dscons.vn"
        old_pwd = "OldPassword@2026"
        new_pwd = "NewPassword@2026Strong"

        # Setup user
        with self.auth_service.get_connection() as conn, conn.cursor() as cur:
            cur.execute("DELETE FROM erp_users WHERE email = %s;", (test_email,))
            conn.commit()

        reg_res = self.auth_service.create_user_account(
            email=test_email, password=old_pwd, full_name="Password Change User"
        )
        user_id = reg_res["id"]

        # Attempt change with wrong old password
        with self.assertRaises(HTTPException) as ctx:
            self.auth_service.change_user_password(user_id, "wrong_old_pwd", new_pwd)
        self.assertEqual(ctx.exception.status_code, 400)

        # Successful change
        change_res = self.auth_service.change_user_password(user_id, old_pwd, new_pwd)
        self.assertEqual(change_res["status"], "success")

        # Old password no longer works
        with self.assertRaises(HTTPException) as ctx:
            self.auth_service.authenticate_email_user(
                email=test_email, password=old_pwd
            )
        self.assertEqual(ctx.exception.status_code, 401)

        # New password works
        login_res = self.auth_service.authenticate_email_user(
            email=test_email, password=new_pwd
        )
        self.assertIn("access_token", login_res)

        # Cleanup
        with self.auth_service.get_connection() as conn, conn.cursor() as cur:
            cur.execute("DELETE FROM erp_users WHERE email = %s;", (test_email,))
            conn.commit()

    def test_google_callback_html_serialization(self):
        from unittest.mock import patch

        from fastapi.testclient import TestClient

        from app.main import create_app

        app = create_app()
        with TestClient(app) as client:
            with patch(
                "app.modules.auth.presentation.auth.auth_service.exchange_google_code"
            ) as mock_exchange:
                mock_exchange.return_value = {
                    "access_token": "test_token",
                    "user": {
                        "id": "eda1319a-ebcb-4bb8-9dbb-e64bdc02e4ff",
                        "email": "dinhsonconstruction@gmail.com",
                        "full_name": "Dinh Son Construction",
                        "role": "superadmin",
                    },
                }
                resp = client.get("/v1/auth/google/callback?code=mock_code")
                self.assertEqual(resp.status_code, 200)
                self.assertIn("JSON.stringify(", resp.text)
                self.assertNotIn("UUID('", resp.text)
                self.assertNotIn("datetime.datetime", resp.text)


if __name__ == "__main__":
    unittest.main()
