"""End-to-end tests for /api/auth/register/* multi-stage flow.

Covers GSTIN validation, OTP issuance via stub mode, OTP verify, and the
atomic /complete endpoint that creates user + company together.
"""
import os
import time
import pytest
import requests

BASE = os.environ.get("REACT_APP_BACKEND_URL", "https://offline-billing-pro-2.preview.emergentagent.com").rstrip("/")


def _s():
    s = requests.Session()
    s.headers.update({"Content-Type": "application/json"})
    return s


# ----------------------------------------------------------------------------
# GSTIN validation
# ----------------------------------------------------------------------------
class TestGstValidate:
    def test_valid_gstin_goa(self):
        s = _s()
        r = s.post(f"{BASE}/api/auth/register/gst-validate", json={"gstin": "30ARLPR3709H1ZT"}, timeout=20)
        assert r.status_code == 200
        d = r.json()
        assert d["valid"] is True
        assert d["state_code"] == "30"
        assert d["state_name"] == "Goa"
        assert d["pan"] == "ARLPR3709H"

    def test_invalid_checksum(self):
        r = _s().post(f"{BASE}/api/auth/register/gst-validate", json={"gstin": "30ARLPR3709H1ZA"}, timeout=20)
        assert r.status_code == 200
        d = r.json()
        assert d["valid"] is False
        assert "checksum" in d["reason"].lower()

    def test_short(self):
        r = _s().post(f"{BASE}/api/auth/register/gst-validate", json={"gstin": "30ABC"}, timeout=20)
        assert r.json()["valid"] is False

    def test_bad_state_code(self):
        # State code 99 doesn't decode (we accept 99 = Centre Jurisdiction, so try 88)
        r = _s().post(f"{BASE}/api/auth/register/gst-validate", json={"gstin": "88ARLPR3709H1ZT"}, timeout=20)
        d = r.json()
        # Either invalid state code OR invalid checksum — both are failures
        assert d["valid"] is False

    def test_lowercase_normalised(self):
        # GSTIN is case-insensitive on input; backend uppercases it
        r = _s().post(f"{BASE}/api/auth/register/gst-validate", json={"gstin": "30arlpr3709h1zt"}, timeout=20)
        assert r.json()["valid"] is True

    def test_empty(self):
        r = _s().post(f"{BASE}/api/auth/register/gst-validate", json={"gstin": ""}, timeout=20)
        assert r.json()["valid"] is False


# ----------------------------------------------------------------------------
# Full registration flow
# ----------------------------------------------------------------------------
@pytest.fixture
def fresh_user():
    """Yields (email, password) and deletes the user after the test."""
    ts = int(time.time() * 1000)
    email = f"reg_test_{ts}@example.com"
    password = "strongpass123"
    yield email, password
    # Cleanup via direct DB
    try:
        import asyncio
        from motor.motor_asyncio import AsyncIOMotorClient
        async def _cleanup():
            cli = AsyncIOMotorClient(os.environ["MONGO_URL"])
            db = cli[os.environ["DB_NAME"]]
            await db.users.delete_many({"email": email})
            await db.companies.delete_many({"email": email})
        asyncio.run(_cleanup())
    except Exception:
        pass


class TestRegisterFlow:
    def test_init_validates_inputs(self):
        # Missing name
        r = _s().post(f"{BASE}/api/auth/register/init", json={
            "first_name": "", "last_name": "X", "email": "a@b.c", "phone": "9876543210", "password": "strongpass123",
        }, timeout=20)
        assert r.status_code == 422

        # Bad email
        r = _s().post(f"{BASE}/api/auth/register/init", json={
            "first_name": "X", "last_name": "Y", "email": "not-an-email", "phone": "9876543210", "password": "strongpass123",
        }, timeout=20)
        assert r.status_code == 422

        # Weak password
        r = _s().post(f"{BASE}/api/auth/register/init", json={
            "first_name": "X", "last_name": "Y", "email": "ok@example.com", "phone": "9876543210", "password": "123",
        }, timeout=20)
        assert r.status_code == 422

        # Common password rejected
        r = _s().post(f"{BASE}/api/auth/register/init", json={
            "first_name": "X", "last_name": "Y", "email": "ok2@example.com", "phone": "9876543210", "password": "password",
        }, timeout=20)
        assert r.status_code == 422

    def test_full_flow_with_valid_gstin(self, fresh_user):
        email, password = fresh_user
        s = _s()
        # 1. init
        r = s.post(f"{BASE}/api/auth/register/init", json={
            "first_name": "Test", "last_name": "User",
            "email": email, "phone": "9876543210", "password": password,
        }, timeout=20)
        assert r.status_code == 200, r.text
        body = r.json()
        assert body["ok"] is True
        assert body["registration_token"]
        assert body["masked_phone"].startswith("+")
        # Stub mode → code visible
        assert body.get("code_preview"), "Need stub-mode OTP for testing"
        code = body["code_preview"]
        token = body["registration_token"]

        # 2. wrong code → 401
        r = s.post(f"{BASE}/api/auth/register/verify-otp", json={
            "registration_token": token, "code": "000000",
        }, timeout=20)
        assert r.status_code == 401

        # 3. correct code → verified token
        r = s.post(f"{BASE}/api/auth/register/verify-otp", json={
            "registration_token": token, "code": code,
        }, timeout=20)
        assert r.status_code == 200
        token2 = r.json()["registration_token"]

        # 4. complete with valid GSTIN
        r = s.post(f"{BASE}/api/auth/register/complete", json={
            "registration_token": token2,
            "company_name": "Test Workspace Pvt Ltd",
            "gstin": "30ARLPR3709H1ZT",
            "business_type": "Retail",
        }, timeout=20)
        assert r.status_code == 200, r.text
        data = r.json()
        assert data["email"] == email
        assert data["gstin"] == "30ARLPR3709H1ZT"
        # Auto-login cookies set
        assert "access_token" in s.cookies.get_dict() or len(s.cookies) > 0

        # 5. /auth/me works with the new session
        me = s.get(f"{BASE}/api/auth/me", timeout=20)
        assert me.status_code == 200
        assert me.json()["email"] == email

    def test_complete_blocks_without_otp_verified(self, fresh_user):
        email, password = fresh_user
        s = _s()
        r = s.post(f"{BASE}/api/auth/register/init", json={
            "first_name": "A", "last_name": "B", "email": email, "phone": "9876543210", "password": password,
        }, timeout=20)
        token = r.json()["registration_token"]
        # Skip the verify step — should be rejected
        r = s.post(f"{BASE}/api/auth/register/complete", json={
            "registration_token": token, "company_name": "Skip Co.",
        }, timeout=20)
        assert r.status_code == 403, r.text

    def test_complete_rejects_invalid_gstin(self, fresh_user):
        email, password = fresh_user
        s = _s()
        r = s.post(f"{BASE}/api/auth/register/init", json={
            "first_name": "A", "last_name": "B", "email": email, "phone": "9876543210", "password": password,
        }, timeout=20)
        body = r.json()
        token, code = body["registration_token"], body["code_preview"]
        v = s.post(f"{BASE}/api/auth/register/verify-otp", json={"registration_token": token, "code": code}, timeout=20)
        token2 = v.json()["registration_token"]
        r = s.post(f"{BASE}/api/auth/register/complete", json={
            "registration_token": token2,
            "company_name": "Bad GST Co",
            "gstin": "30ARLPR3709H1ZA",  # wrong checksum
        }, timeout=20)
        assert r.status_code == 422
        assert "GSTIN invalid" in r.json()["detail"]

    def test_complete_requires_company_name(self, fresh_user):
        email, password = fresh_user
        s = _s()
        r = s.post(f"{BASE}/api/auth/register/init", json={
            "first_name": "A", "last_name": "B", "email": email, "phone": "9876543210", "password": password,
        }, timeout=20)
        body = r.json()
        v = s.post(f"{BASE}/api/auth/register/verify-otp", json={
            "registration_token": body["registration_token"], "code": body["code_preview"],
        }, timeout=20)
        token2 = v.json()["registration_token"]
        r = s.post(f"{BASE}/api/auth/register/complete", json={
            "registration_token": token2, "company_name": "",
        }, timeout=20)
        assert r.status_code == 422

    def test_init_duplicate_email_rejected(self, fresh_user):
        """First /init succeeds; running again with same email AFTER /complete should 409."""
        email, password = fresh_user
        s = _s()
        # 1. complete a registration
        r = s.post(f"{BASE}/api/auth/register/init", json={
            "first_name": "A", "last_name": "B", "email": email, "phone": "9876543210", "password": password,
        }, timeout=20)
        body = r.json()
        v = s.post(f"{BASE}/api/auth/register/verify-otp", json={
            "registration_token": body["registration_token"], "code": body["code_preview"],
        }, timeout=20)
        s.post(f"{BASE}/api/auth/register/complete", json={
            "registration_token": v.json()["registration_token"], "company_name": "Dup Test",
        }, timeout=20)
        # 2. /init again with same email → 409
        r2 = _s().post(f"{BASE}/api/auth/register/init", json={
            "first_name": "A", "last_name": "B", "email": email, "phone": "9876543210", "password": password,
        }, timeout=20)
        assert r2.status_code == 409

    def test_bad_token_rejected(self):
        s = _s()
        r = s.post(f"{BASE}/api/auth/register/verify-otp", json={
            "registration_token": "garbage.token.here", "code": "123456",
        }, timeout=20)
        assert r.status_code == 401
        r = s.post(f"{BASE}/api/auth/register/complete", json={
            "registration_token": "garbage.token.here", "company_name": "X",
        }, timeout=20)
        assert r.status_code == 401
