package test.it.cases;

import test.it.utils.TestConfig;
import test.it.utils.json.JsonBuilders;
import test.it.utils.json.JsonParsers;
import test.it.utils.log.TestLog;
import test.it.utils.log.TestResult;
import test.it.utils.ws.WsSession;
import utils.crypto.Ed25519Util;
import shine.db.dao.SolanaUsersDAO;
import shine.db.entities.SolanaUserEntry;

import java.time.Duration;
import java.util.Base64;

import static org.junit.jupiter.api.Assertions.*;

public class IT_07_EspPairing {

    private static final String LOGIN = TestConfig.LOGIN();

    public static void main(String[] args) {
        TestLog.info("Standalone: при необходимости локально создаю тестового пользователя напрямую в БД");
        String summary = run();
        System.out.println(summary);
    }

    public static String run() {
        TestResult r = new TestResult("IT_07_EspPairing");
        Duration t = Duration.ofSeconds(5);

        try {
            ensureUserSeeded();
            Session clientSession = createSession(LOGIN, 1, "Web", t);

            try (WsSession requesterWs = WsSession.open();
                 WsSession clientWs = WsSession.open()) {

                sessionLogin2Steps(clientWs, clientSession, 1, "Web", t, r);

                String passwordHash = "argon2id$v=19$m=65536,t=2,p=1$test$esp_pairing_hash";
                String upsertResp = clientWs.call(
                        "UpsertEspPairingSettings",
                        JsonBuilders.upsertEspPairingSettings(true, passwordHash, 180),
                        t
                );
                assertEquals(200, JsonParsers.status(upsertResp), "UpsertEspPairingSettings must be 200");

                SessionMaterial requesterMaterial = newSessionMaterial();
                String startResp = requesterWs.call(
                        "StartEspPairing",
                        JsonBuilders.startEspPairing(LOGIN, passwordHash, requesterMaterial.sessionKey(), 1, "Android", 1),
                        t
                );
                assertEquals(200, JsonParsers.status(startResp), "StartEspPairing must be 200");
                String pairingId = JsonParsers.payloadText(startResp, "pairingId");
                assertNotNull(pairingId, "pairingId must be present");
                assertEquals("created", JsonParsers.payloadText(startResp, "state"));
                assertEquals(Boolean.TRUE, JsonParsers.payloadBoolean(startResp, "trustedSessionOnline"));

                String listResp = clientWs.call("ListEspPairingRequests", JsonBuilders.listEspPairingRequests(), t);
                assertEquals(200, JsonParsers.status(listResp), "ListEspPairingRequests must be 200");
                assertTrue(listResp.contains(pairingId), "ListEspPairingRequests must contain created pairingId");

                String approveResp = clientWs.call(
                        "ApproveEspPairing",
                        JsonBuilders.approveEspPairing(pairingId, "AQIDBA=="),
                        t
                );
                assertEquals(200, JsonParsers.status(approveResp), "ApproveEspPairing must be 200");
                assertEquals("approved", JsonParsers.payloadText(approveResp, "state"));

                String statusResp = requesterWs.call(
                        "GetEspPairingStatus",
                        JsonBuilders.getEspPairingStatus(pairingId),
                        t
                );
                assertEquals(200, JsonParsers.status(statusResp), "GetEspPairingStatus must be 200");
                assertEquals("approved", JsonParsers.payloadText(statusResp, "state"));
                assertEquals("AQIDBA==", JsonParsers.payloadText(statusResp, "encryptedPayload"));

                String forbiddenResp = requesterWs.call(
                        "ListEspPairingRequests#anonymous",
                        JsonBuilders.listEspPairingRequests(),
                        t
                );
                assertErrorFormat(forbiddenResp, "ListEspPairingRequests", "PAIRING_REQUIRES_AUTH_SESSION");

                r.ok("ESP pairing: обычная доверенная сессия увидела запрос и подтвердила зашифрованный payload");
            }
        } catch (Throwable e) {
            r.fail("IT_07_EspPairing упал: " + e.getMessage());
        }

        return r.summaryLine();
    }

    private static Session createSession(String login, int sessionType, String clientPlatform, Duration t) {
        try (WsSession ws = WsSession.open()) {
            String nonceResp = ws.call("AuthChallenge", JsonBuilders.authChallenge(login), t);
            assertEquals(200, JsonParsers.status(nonceResp));
            String authNonce = JsonParsers.authNonce(nonceResp);
            assertNotNull(authNonce);

            SessionMaterial material = newSessionMaterial();
            String storagePwd = TestConfig.fakeStoragePwd();
            String createResp = ws.call(
                    "CreateAuthSession",
                    JsonBuilders.createAuthSessionV2(login, authNonce, storagePwd, material.sessionKey(), sessionType, clientPlatform),
                    t
            );
            assertEquals(200, JsonParsers.status(createResp), "CreateAuthSession must be 200");
            String sessionId = JsonParsers.sessionId(createResp);
            assertNotNull(sessionId);
            return new Session(sessionId, material.sessionKey(), material.sessionPrivKey(), storagePwd);
        }
    }

    private static void sessionLogin2Steps(WsSession ws,
                                           Session s,
                                           int sessionType,
                                           String clientPlatform,
                                           Duration t,
                                           TestResult r) {
        String chResp = ws.call("SessionChallenge", JsonBuilders.sessionChallenge(s.sessionId()), t);
        assertEquals(200, JsonParsers.status(chResp));
        String nonce = JsonParsers.sessionNonce(chResp);
        assertNotNull(nonce);

        String loginResp = ws.call(
                "SessionLogin",
                JsonBuilders.sessionLogin(s.sessionId(), s.sessionKey(), nonce, s.sessionPrivKey(), sessionType, clientPlatform),
                t
        );
        assertEquals(200, JsonParsers.status(loginResp));
        assertEquals(s.storagePwd(), JsonParsers.storagePwd(loginResp));
        r.ok("SessionLogin OK for sessionType=" + sessionType);
    }

    private static void assertErrorFormat(String resp, String op, String code) {
        int status = JsonParsers.status(resp);
        assertFalse(status >= 200 && status < 300, "Expected non-2xx status: " + resp);
        assertEquals(Boolean.FALSE, JsonParsers.ok(resp), "Expected ok=false: " + resp);
        assertEquals(op, JsonParsers.op(resp), "Unexpected op: " + resp);
        assertEquals(code, JsonParsers.errorCode(resp), "Unexpected error code: " + resp);
    }

    private static SessionMaterial newSessionMaterial() {
        byte[] sessionPrivKey = Ed25519Util.generatePrivateKey();
        byte[] sessionPubKey = Ed25519Util.derivePublicKey(sessionPrivKey);
        String sessionKey = "ed25519/" + Base64.getEncoder().encodeToString(sessionPubKey);
        return new SessionMaterial(sessionKey, sessionPrivKey);
    }

    private static void ensureUserSeeded() throws Exception {
        if (SolanaUsersDAO.getInstance().existsByLogin(LOGIN)) {
            return;
        }
        SolanaUserEntry entry = new SolanaUserEntry();
        entry.setLogin(LOGIN);
        entry.setBlockchainName(TestConfig.getBlockchainName(LOGIN));
        entry.setSolanaKey(TestConfig.solanaPublicKeyB64(LOGIN));
        entry.setBlockchainKey(TestConfig.blockchainPublicKeyB64(LOGIN));
        entry.setDeviceKey(TestConfig.devicePublicKeyB64(LOGIN));
        SolanaUsersDAO.getInstance().insert(entry);
    }

    private record Session(String sessionId, String sessionKey, byte[] sessionPrivKey, String storagePwd) {}
    private record SessionMaterial(String sessionKey, byte[] sessionPrivKey) {}
}
