"""
Backend tests for the new Trial + Premium and user self-service profile endpoints.

Modules covered:
- POST /api/trial/status (anti-reinstall hardware-tied trial)
- PUT  /api/user/profile (regular user changes own username/password)
- Existing auth + device routes (regression smoke check)
"""
import os
import time
import uuid
import subprocess
from datetime import datetime, timezone, timedelta

import pytest
import requests

BASE_URL = (
    os.environ.get("BACKEND_URL")
    or os.environ.get("REACT_APP_BACKEND_URL")
    or "https://nadiplayer-edit.preview.emergentagent.com"
).rstrip("/")

ADMIN_USER = os.environ.get("ADMIN_USERNAME", "nadinadi")
ADMIN_PASS = os.environ.get("ADMIN_PASSWORD", "0644782611")


# ---------- Fixtures ----------
@pytest.fixture(scope="session")
def api():
    s = requests.Session()
    s.headers.update({"Content-Type": "application/json"})
    return s


@pytest.fixture(scope="session")
def admin_token(api):
    r = api.post(f"{BASE_URL}/api/auth/login",
                 json={"username": ADMIN_USER, "password": ADMIN_PASS})
    assert r.status_code == 200, f"admin login failed: {r.status_code} {r.text}"
    return r.json()["access_token"]


@pytest.fixture(scope="session")
def admin_client(api, admin_token):
    s = requests.Session()
    s.headers.update({
        "Content-Type": "application/json",
        "Authorization": f"Bearer {admin_token}",
    })
    return s


@pytest.fixture
def fresh_user(admin_client):
    """Create a fresh test user, return (username, password, id, token).
    Cleans up via admin DELETE at teardown."""
    suffix = uuid.uuid4().hex[:6]
    username = f"test_{suffix}"
    password = "pw1234"
    r = admin_client.post(f"{BASE_URL}/api/admin/users", json={
        "username": username, "password": password,
        "days": 30, "xtream_mode": "shared",
    })
    assert r.status_code in (200, 201), f"create user failed: {r.status_code} {r.text}"
    user = r.json()
    # Login as that user to get JWT
    lr = requests.post(f"{BASE_URL}/api/auth/login",
                       json={"username": username, "password": password})
    assert lr.status_code == 200, lr.text
    token = lr.json()["access_token"]
    yield {"username": username, "password": password, "id": user["id"], "token": token}
    # teardown
    try:
        admin_client.delete(f"{BASE_URL}/api/admin/users/{user['id']}")
    except Exception:
        pass


# ---------- /api/trial/status ----------
class TestTrialStatus:
    def test_missing_hardware_id_returns_422_or_400(self, api):
        r = api.post(f"{BASE_URL}/api/trial/status", json={})
        # Pydantic validation -> 422; manual check -> 400. Either is acceptable.
        assert r.status_code in (400, 422), r.text

    def test_short_hardware_id_rejected(self, api):
        # min_length=4 in the model
        r = api.post(f"{BASE_URL}/api/trial/status", json={"hardware_id": "a"})
        assert r.status_code in (400, 422), r.text

    def test_fresh_hardware_returns_zero_days_used(self, api):
        hw = f"TEST-HW-{uuid.uuid4().hex}"
        r = api.post(f"{BASE_URL}/api/trial/status", json={
            "hardware_id": hw, "model": "PyTest", "app_version": "1.0.0",
        })
        assert r.status_code == 200, r.text
        d = r.json()
        assert d["hardware_id"] == hw
        assert d["days_used"] == 0
        assert d["trial_days"] == 30
        assert d["days_remaining"] == 30
        assert d["trial_expired"] is False
        assert d["is_premium"] is False
        assert d["first_seen_at"] == d["last_seen_at"]
        assert d["premium_expiry"] in (None, "")

    def test_repeat_call_preserves_first_seen_at(self, api):
        """Anti-reinstall — second call MUST NOT reset first_seen_at."""
        hw = f"TEST-HW-{uuid.uuid4().hex}"
        r1 = api.post(f"{BASE_URL}/api/trial/status",
                      json={"hardware_id": hw, "model": "first"}).json()
        time.sleep(1.2)
        r2 = api.post(f"{BASE_URL}/api/trial/status",
                      json={"hardware_id": hw, "model": "second"}).json()
        assert r1["first_seen_at"] == r2["first_seen_at"], (
            "first_seen_at was overwritten on repeated call — anti-reinstall broken"
        )
        # last_seen_at should advance
        assert r2["last_seen_at"] >= r1["last_seen_at"]
        # last_seen_at should be > first_seen_at on the 2nd call
        assert r2["last_seen_at"] > r2["first_seen_at"]

    def test_backdated_trial_is_expired(self, api):
        """Backdate first_seen_at via mongosh, then verify trial_expired=True."""
        hw = f"TEST-HW-EXPIRED-{uuid.uuid4().hex}"
        # Create the doc
        r = api.post(f"{BASE_URL}/api/trial/status", json={"hardware_id": hw})
        assert r.status_code == 200
        # Backdate first_seen_at to 60 days ago
        backdate_iso = (datetime.now(timezone.utc) - timedelta(days=60)).isoformat()
        cmd = [
            "mongosh", os.environ.get("DB_NAME", "nadiplayer"),
            "--quiet", "--eval",
            f'db.device_trials.updateOne({{hardware_id: "{hw}"}}, {{$set: {{first_seen_at: "{backdate_iso}"}}}})',
        ]
        proc = subprocess.run(cmd, capture_output=True, text=True, timeout=10)
        if proc.returncode != 0:
            pytest.skip(f"mongosh unavailable: {proc.stderr}")
        # Re-query
        r2 = api.post(f"{BASE_URL}/api/trial/status", json={"hardware_id": hw})
        assert r2.status_code == 200, r2.text
        d = r2.json()
        assert d["trial_expired"] is True
        assert d["days_remaining"] == 0
        assert d["days_used"] >= 30

    def test_get_method_not_allowed(self, api):
        r = api.get(f"{BASE_URL}/api/trial/status")
        assert r.status_code in (404, 405)


# ---------- /api/user/profile ----------
class TestUserProfile:
    def test_unauthenticated_returns_401(self, api):
        r = api.put(f"{BASE_URL}/api/user/profile",
                    json={"current_password": "x", "new_username": "abc"})
        assert r.status_code == 401, r.text

    def test_admin_token_returns_403(self, api, admin_token):
        r = requests.put(
            f"{BASE_URL}/api/user/profile",
            headers={"Authorization": f"Bearer {admin_token}",
                     "Content-Type": "application/json"},
            json={"current_password": ADMIN_PASS, "new_username": "newadmin"},
        )
        assert r.status_code == 403, r.text
        assert "Admin" in r.json().get("detail", "")

    def test_wrong_current_password_returns_401(self, api, fresh_user):
        r = requests.put(
            f"{BASE_URL}/api/user/profile",
            headers={"Authorization": f"Bearer {fresh_user['token']}",
                     "Content-Type": "application/json"},
            json={"current_password": "WRONG", "new_username": "newname"},
        )
        assert r.status_code == 401, r.text

    def test_change_username_lowercased_and_token_works(self, api, fresh_user):
        new_username = f"renamed_{uuid.uuid4().hex[:5]}".upper()  # uppercase to test lowering
        r = requests.put(
            f"{BASE_URL}/api/user/profile",
            headers={"Authorization": f"Bearer {fresh_user['token']}",
                     "Content-Type": "application/json"},
            json={"current_password": fresh_user["password"],
                  "new_username": new_username},
        )
        assert r.status_code == 200, r.text
        body = r.json()
        assert "access_token" in body
        assert body["user"]["username"] == new_username.lower()
        # Login with new lowercase username works
        lr = requests.post(f"{BASE_URL}/api/auth/login", json={
            "username": new_username.lower(),
            "password": fresh_user["password"],
        })
        assert lr.status_code == 200, lr.text
        # /api/auth/me with the returned token
        me = requests.get(f"{BASE_URL}/api/auth/me",
                          headers={"Authorization": f"Bearer {body['access_token']}"})
        assert me.status_code == 200
        assert me.json()["username"] == new_username.lower()

    def test_change_password(self, api, fresh_user):
        new_pw = "brandNew99"
        r = requests.put(
            f"{BASE_URL}/api/user/profile",
            headers={"Authorization": f"Bearer {fresh_user['token']}",
                     "Content-Type": "application/json"},
            json={"current_password": fresh_user["password"], "new_password": new_pw},
        )
        assert r.status_code == 200, r.text
        # Old password fails
        bad = requests.post(f"{BASE_URL}/api/auth/login", json={
            "username": fresh_user["username"], "password": fresh_user["password"]})
        assert bad.status_code == 401
        # New password works
        good = requests.post(f"{BASE_URL}/api/auth/login", json={
            "username": fresh_user["username"], "password": new_pw})
        assert good.status_code == 200

    def test_username_collision_returns_409(self, admin_client, fresh_user):
        # Create a 2nd user, then have fresh_user try to rename to that username
        other = f"other_{uuid.uuid4().hex[:5]}"
        cr = admin_client.post(f"{BASE_URL}/api/admin/users", json={
            "username": other, "password": "pw1234",
            "days": 30, "xtream_mode": "shared",
        })
        assert cr.status_code in (200, 201), cr.text
        other_id = cr.json()["id"]
        try:
            r = requests.put(
                f"{BASE_URL}/api/user/profile",
                headers={"Authorization": f"Bearer {fresh_user['token']}",
                         "Content-Type": "application/json"},
                json={"current_password": fresh_user["password"],
                      "new_username": other},
            )
            assert r.status_code == 409, r.text
        finally:
            admin_client.delete(f"{BASE_URL}/api/admin/users/{other_id}")

    def test_no_changes_returns_400(self, api, fresh_user):
        """Sending only current_password without new fields = nothing to update."""
        r = requests.put(
            f"{BASE_URL}/api/user/profile",
            headers={"Authorization": f"Bearer {fresh_user['token']}",
                     "Content-Type": "application/json"},
            json={"current_password": fresh_user["password"]},
        )
        assert r.status_code == 400, r.text


# ---------- Regression: existing endpoints still work ----------
class TestExistingRoutes:
    def test_admin_login_and_me(self, api, admin_token):
        me = requests.get(f"{BASE_URL}/api/auth/me",
                          headers={"Authorization": f"Bearer {admin_token}"})
        assert me.status_code == 200, me.text
        assert me.json()["role"] == "admin"
        assert me.json()["username"] == ADMIN_USER.lower()

    def test_device_register_status_unpair(self, api):
        # register — server generates its own device_id; no body needed
        r = api.post(f"{BASE_URL}/api/device/register", json={})
        assert r.status_code == 200, r.text
        device_id = r.json()["device_id"]
        # status
        s = api.get(f"{BASE_URL}/api/device/status/{device_id}")
        assert s.status_code == 200, s.text
        body = s.json()
        assert body["device_id"] == device_id
        assert body.get("paired") is False
        # unpair (idempotent — device may not be paired; just ensure no 5xx)
        u = api.delete(f"{BASE_URL}/api/device/unpair/{device_id}")
        assert u.status_code < 500, u.text
