#!/usr/bin/env python3
"""Regression tests for FIFA 17's account-scoped phishing/security gate."""
import importlib
import json
import os
import sys
import tempfile

TOOLS = os.path.dirname(os.path.abspath(__file__))
if TOOLS not in sys.path:
    sys.path.insert(0, TOOLS)

DEVICE_ID = "1" * 32
TRANSFORMED_ANSWER = "a" * 32  # sanitized replay value, not a real answer


class Request:
    def __init__(self, method, path, sid=None):
        self.command = method
        self.path = path
        self.headers = {"X-UT-SID": sid} if sid is not None else {}
        self._body = b""


def request(utas_server, method, suffix, sid=None):
    sid = utas_server.SID if sid is None else sid
    h = Request(method, "/ut/game/fifa17/phishing/" + suffix, sid)
    return utas_server.security_question_route(h)


def profile(state, persona_id):
    path = os.path.join(state, "accounts", str(persona_id), "fifa17_profile.json")
    with open(path) as f:
        return json.load(f)


def main():
    with tempfile.TemporaryDirectory() as state:
        os.environ["FUT_ACCOUNT_PATH"] = os.path.join(state, "active_account.json")
        os.environ["FUT_PROFILE_ROOT"] = os.path.join(state, "accounts")
        os.environ.pop("FUT_PROFILE", None)

        import fut_account
        import fut_store
        import fut_accounts
        import utas_server
        importlib.reload(fut_account)
        importlib.reload(fut_store)
        importlib.reload(fut_accounts)
        importlib.reload(utas_server)

        # New/missing state: launcher account selection initializes one account only.
        fut_accounts.activate({"personaId": 771001, "personaName": "SEC_A"})
        p = profile(state, 771001)
        assert p["securityQuestion"] == {"version": 1, "verified": True}

        # Actual trusted-device response fields parsed by CardsDLL 0x18012a170.
        code, body = request(
            utas_server, "GET", "trusteddevice?deviceId=" + DEVICE_ID)
        assert code == 200
        assert body == {
            "changed": False,
            "exists": True,
            "locked": False,
            "trusted": True,
        }

        # Existing initialized state survives a fresh Store instance/process view.
        reopened = fut_store.Store(fut_store.profile_path_for(771001))
        assert reopened.profile()["securityQuestion"] == {
            "version": 1, "verified": True}

        # FIFA's observed repeat-session request: POST, empty body, opaque 32-hex
        # deviceId and transformed answer in the query string. The answer is accepted
        # for OpenFUT compatibility but never persisted.
        code, body = request(
            utas_server,
            "POST",
            "validate?deviceId=%s&answer=%s" % (DEVICE_ID, TRANSFORMED_ANSWER),
        )
        assert (code, body) == (200, {})
        saved = profile(state, 771001)
        assert TRANSFORMED_ANSWER not in json.dumps(saved)

        # Question lookup uses the three fields parsed by CardsDLL 0x180129850.
        code, body = request(
            utas_server, "GET", "question?deviceId=" + DEVICE_ID)
        assert code == 200
        assert set(body) == {"question", "attempts", "recoverAttempts"}
        assert all(isinstance(body[k], int) for k in body)

        # Malformed values/methods and missing sessions fail explicitly.
        code, _ = request(utas_server, "POST", "validate?deviceId=bad&answer=bad")
        assert code == 400
        code, _ = request(
            utas_server, "DELETE", "trusteddevice?deviceId=" + DEVICE_ID)
        assert code == 405
        h = Request(
            "GET", "/ut/game/fifa17/phishing/trusteddevice?deviceId=" + DEVICE_ID)
        code, _ = utas_server.security_question_route(h)
        assert code == 400

        # Ordinary request logging must redact answer query values.
        raw_path = "/ut/game/fifa17/phishing/validate?deviceId=%s&answer=%s" % (
            DEVICE_ID, TRANSFORMED_ANSWER)
        safe_path = utas_server.safe_request_path(raw_path)
        assert TRANSFORMED_ANSWER not in safe_path
        assert "answer=%5BREDACTED%5D" in safe_path

        # Multiple profiles receive independent persisted state; selecting B must not
        # alter A's initialized record.
        fut_accounts.activate({"personaId": 771002, "personaName": "SEC_B"})
        assert profile(state, 771002)["securityQuestion"] == {
            "version": 1, "verified": True}
        assert profile(state, 771001)["securityQuestion"] == {
            "version": 1, "verified": True}

        # Legacy profile with the field removed is repaired once and persisted.
        b_path = os.path.join(state, "accounts", "771002", "fifa17_profile.json")
        b = profile(state, 771002)
        b.pop("securityQuestion")
        with open(b_path, "w") as f:
            json.dump(b, f)
        fut_store.STORE._p = None
        code, body = request(
            utas_server, "GET", "trusteddevice?deviceId=" + DEVICE_ID)
        assert code == 200 and body["exists"] and body["trusted"]
        assert profile(state, 771002)["securityQuestion"]["verified"] is True

    print("security-question compatibility: PASS")


if __name__ == "__main__":
    main()
