"""
Test suite for RDP Manager.

WinRM calls are mocked, so these tests run anywhere (Linux/CI) without any real
Windows target. They cover auth gating, machine CRUD, provisioning logic,
at-rest encryption, exports and password change.
"""

import os
import sys
import tempfile

# Configure an isolated instance dir + known login password BEFORE importing app.
_TMP = tempfile.mkdtemp(prefix="rdp-test-")
os.environ["RDP_INSTANCE_DIR"] = _TMP
os.environ["RDP_MANAGER_PASSWORD"] = "test-pass-123"
os.environ["RDP_MANAGER_EMAIL"] = "admin@test.local"
os.environ["DATABASE_URL"] = f"sqlite:///{_TMP}/test.db"

sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

import pytest  # noqa: E402

import app as rdp  # noqa: E402

LOGIN = "test-pass-123"
LOGIN_EMAIL = "admin@test.local"


@pytest.fixture()
def client(monkeypatch):
    rdp.app.config["TESTING"] = True
    with rdp.app.app_context():
        rdp.db.drop_all()
        rdp.db.create_all()
        rdp._ensure_settings()

    # Mock all WinRM interactions with successful responses.
    monkeypatch.setattr(rdp, "execute_ps", lambda machine, pw, cmd: (True, "OK", ""))
    monkeypatch.setattr(rdp, "connect_machine", lambda machine, pw: (True, "Connected"))

    with rdp.app.test_client() as c:
        yield c


def _login(client):
    return client.post("/api/login", json={"email": LOGIN_EMAIL, "password": LOGIN})


def _add_machine(client, name="server-01"):
    return client.post(
        "/api/machines",
        json={"name": name, "host": "10.0.0.5", "admin_username": "Administrator"},
    )


# --------------------------------------------------------------------------- #
# Authentication
# --------------------------------------------------------------------------- #


def test_requires_auth(client):
    assert client.get("/api/machines").status_code == 401


def test_login_wrong_password(client):
    resp = client.post("/api/login", json={"email": LOGIN_EMAIL, "password": "nope"})
    assert resp.status_code == 401
    assert resp.get_json()["success"] is False


def test_login_wrong_email(client):
    resp = client.post("/api/login", json={"email": "other@test.local", "password": LOGIN})
    assert resp.status_code == 401


def test_login_success(client):
    resp = _login(client)
    assert resp.status_code == 200
    assert client.get("/api/machines").status_code == 200
    assert client.get("/api/me").get_json()["email"] == LOGIN_EMAIL


def test_change_password(client):
    _login(client)
    bad = client.post("/api/change-password", json={"current_password": "x", "new_password": "longenough"})
    assert bad.status_code == 401
    short = client.post("/api/change-password", json={"current_password": LOGIN, "new_password": "short"})
    assert short.status_code == 400
    ok = client.post("/api/change-password", json={"current_password": LOGIN, "new_password": "brand-new-pass"})
    assert ok.status_code == 200
    # old password no longer works
    client.post("/api/logout")
    assert client.post("/api/login", json={"email": LOGIN_EMAIL, "password": LOGIN}).status_code == 401
    assert client.post("/api/login", json={"email": LOGIN_EMAIL, "password": "brand-new-pass"}).status_code == 200


def test_change_email(client):
    _login(client)
    bad_email = client.post("/api/change-password", json={"current_password": LOGIN, "new_email": "not-an-email"})
    assert bad_email.status_code == 400
    ok = client.post("/api/change-password", json={"current_password": LOGIN, "new_email": "new@test.local"})
    assert ok.status_code == 200
    client.post("/api/logout")
    # login now requires the new email
    assert client.post("/api/login", json={"email": LOGIN_EMAIL, "password": LOGIN}).status_code == 401
    assert client.post("/api/login", json={"email": "new@test.local", "password": LOGIN}).status_code == 200


def test_change_requires_something(client):
    _login(client)
    resp = client.post("/api/change-password", json={"current_password": LOGIN})
    assert resp.status_code == 400


# --------------------------------------------------------------------------- #
# Machines
# --------------------------------------------------------------------------- #


def test_add_and_list_machine(client):
    _login(client)
    assert _add_machine(client).status_code == 201
    data = client.get("/api/machines").get_json()
    assert len(data) == 1
    assert data[0]["name"] == "server-01"
    assert data[0]["rdp_user_count"] == 0


def test_add_machine_invalid_name(client):
    _login(client)
    resp = client.post(
        "/api/machines",
        json={"name": "bad name!;", "host": "h", "admin_username": "a"},
    )
    assert resp.status_code == 400


def test_add_machine_duplicate(client):
    _login(client)
    _add_machine(client)
    assert _add_machine(client).status_code == 409


def test_delete_machine(client):
    _login(client)
    mid = _add_machine(client).get_json()["id"]
    assert client.delete(f"/api/machines/{mid}").status_code == 200
    assert client.delete(f"/api/machines/{mid}").status_code == 404


# --------------------------------------------------------------------------- #
# Provisioning
# --------------------------------------------------------------------------- #


def test_configure_single(client):
    _login(client)
    mid = _add_machine(client).get_json()["id"]
    resp = client.post(f"/api/configure/{mid}", json={"admin_password": "adminpw"})
    assert resp.status_code == 200
    body = resp.get_json()
    assert body["success"] is True
    assert body["rdp_username"] == "rdp-server-01-1"
    assert len(body["rdp_password"]) >= 12


def test_bulk_configure_count(client):
    _login(client)
    mid = _add_machine(client).get_json()["id"]
    resp = client.post(f"/api/bulk-configure/{mid}", json={"admin_password": "pw", "count": 3})
    assert resp.status_code == 200
    assert len(resp.get_json()["created_users"]) == 3
    # usernames are unique/sequential
    names = [u["username"] for u in resp.get_json()["created_users"]]
    assert names == ["rdp-server-01-1", "rdp-server-01-2", "rdp-server-01-3"]


def test_bulk_configure_rejects_out_of_range(client):
    _login(client)
    mid = _add_machine(client).get_json()["id"]
    assert client.post(f"/api/bulk-configure/{mid}", json={"admin_password": "pw", "count": 0}).status_code == 400
    assert client.post(f"/api/bulk-configure/{mid}", json={"admin_password": "pw", "count": 51}).status_code == 400


def test_configure_requires_admin_password(client):
    _login(client)
    mid = _add_machine(client).get_json()["id"]
    assert client.post(f"/api/configure/{mid}", json={}).status_code == 400


# --------------------------------------------------------------------------- #
# Credentials, encryption at rest, exports
# --------------------------------------------------------------------------- #


def test_password_encrypted_at_rest(client):
    _login(client)
    mid = _add_machine(client).get_json()["id"]
    plaintext = client.post(f"/api/configure/{mid}", json={"admin_password": "pw"}).get_json()["rdp_password"]

    with rdp.app.app_context():
        stored = rdp.RDPUser.query.first().rdp_password
        assert stored != plaintext  # not stored in clear
        assert rdp.decrypt_secret(stored) == plaintext  # round-trips


def test_get_credentials(client):
    _login(client)
    mid = _add_machine(client).get_json()["id"]
    client.post(f"/api/bulk-configure/{mid}", json={"admin_password": "pw", "count": 2})
    data = client.get(f"/api/credentials/{mid}").get_json()
    assert data["total_users"] == 2
    assert all(len(u["rdp_password"]) >= 12 for u in data["users"])


def test_export_csv_and_json(client):
    _login(client)
    mid = _add_machine(client).get_json()["id"]
    client.post(f"/api/configure/{mid}", json={"admin_password": "pw"})

    csv_resp = client.get("/api/export-csv")
    assert csv_resp.status_code == 200
    assert b"rdp-server-01-1" in csv_resp.data

    json_resp = client.get("/api/export-json")
    assert json_resp.status_code == 200
    payload = json_resp.get_json()
    assert payload["machines"][0]["users"][0]["rdp_username"] == "rdp-server-01-1"


def test_generate_password_strength():
    pw = rdp.generate_password(24)
    assert len(pw) == 24
    assert any(c.isupper() for c in pw)
    assert any(c.islower() for c in pw)
    assert any(c.isdigit() for c in pw)
    assert any(c in rdp._PW_SPECIAL for c in pw)
    # no characters that would break single-quoted PowerShell strings
    assert not any(c in pw for c in "\"`$'")
