import { describe, test, expect, beforeAll } from "bun:test";
import { waitForService, baseUrl, doorName, buzzerNumber, key, buzzerUrl } from "./testCommon";
import { DoorStatus } from "../src/types/DoorStatus";
import { StatusResponse } from "../src/functions/api/door/status";
import { sleep } from "bun";
import { ONBOARDING_DOOR_NAME, ONBOARDING_DOOR_PIN } from "../src/schema/DoorConfig";
import { LogCallResponse } from "../src/functions/api/door/logCall";
import { CheckOtpResponse } from "../src/functions/api/door/checkOtp";

// these tests should only run locally
if (process.env.STAGE === 'staging') {
  process.exit(0);
}

beforeAll(async () => {
  await waitForService(baseUrl);
});

describe("info path works", () => {
  test("info works from UI", async () => {
    const resp = await fetch(baseUrl + `/api/door/info?door=${doorName}`).then(res => res.json()) as any;

    expect(resp.id as string).toBe(doorName);
    expect(resp.buzzer).toBe(buzzerNumber);
  });

  test("unknown info repsonds with a msg", async () => {
    const resp = await fetch(baseUrl + `/api/door/info?door=baddoor`);

    expect(resp.status).toBe(404);
    const body = await resp.json() as any;
    expect(body.msg).toContain("not registered");

  })

  test("info works from client", async () => {
    const resp = await fetch(baseUrl + `/api/door/info?buzzer=${buzzerNumber}`).then(res => res.json()) as any;

    // TODO: why is this different?
    expect(resp.door as string).toBe(doorName);
    expect(resp.buzzer).toBe(buzzerNumber);
  });
});

describe("unlock path works", () => {
  test("auth updates info and status relocks auth", async () => {
    // run status first, to make sure we are closed    
    const statusReset = await fetch(baseUrl + `/api/door/status?door=${doorName}`).then(res => res.json()) as StatusResponse;

    const resp = await fetch(baseUrl + `/api/door/info?door=${doorName}`).then(res => res.json()) as any;

    expect(resp.id).toBe(doorName);
    expect(resp.status).toBe(DoorStatus.CLOSED);

    // unlock door with wrong code should be 401
    const badAuthResp = await fetch(baseUrl + `/api/door/auth?door=${doorName}&key=thisisthewrongkey`);
    expect(badAuthResp.status).toBe(401);

    // unlock door with correct code should be 200
    const authResp = await fetch(baseUrl + `/api/door/auth?door=${doorName}&key=${key}`);
    expect(authResp.status).toBe(200);

    // door should be unlocked on info route
    const infoOpen = await fetch(baseUrl + `/api/door/info?door=${doorName}`).then(res => res.json()) as any;
    expect(infoOpen.status).toBe(DoorStatus.OPEN);

    // calling status should unlock and close the door
    const statusOpen = await fetch(baseUrl + `/api/door/status?door=${doorName}`).then(res => res.json()) as StatusResponse;
    expect(statusOpen.status).toBe(DoorStatus.OPEN);

    const infoClosed = await fetch(baseUrl + `/api/door/info?door=${doorName}`).then(res => res.json()) as any;
    expect(infoClosed.status).toBe(DoorStatus.CLOSED);
  });

  test("auth works for timeout", async () => {
    // run status first, to make sure we are closed
    const statusReset = await fetch(baseUrl + `/api/door/status?door=${doorName}`).then(res => res.json()) as StatusResponse;

    // run auth with timeout specified
    const authResp = await fetch(baseUrl + `/api/door/auth?door=${doorName}&key=${key}&timeout=1`);

    // sleep 1s
    await sleep(1_000);

    // we should be closed, because we passed the timeout
    const infoClosed = await fetch(baseUrl + `/api/door/info?door=${doorName}`).then(res => res.json()) as any;
    expect(infoClosed.status).toBe(DoorStatus.CLOSED);
  });
});

describe("call log path works", () => {
  test("call log returns nothing when onboarding is not enabled", async () => {
    // run status first, to make sure we are closed
    const statusReset = await fetch(baseUrl + `/api/door/status?door=${ONBOARDING_DOOR_NAME}`).then(res => res.json()) as StatusResponse;

    // try to log call
    const logCallRes = await fetch(baseUrl + `/api/door/logCall?caller=${buzzerNumber}`);
    expect(logCallRes.status).toBe(400);
  });

  test("call log returns 4 digit OTP when onboarding enabled", async () => {
    // run status first, to make sure we are closed
    const statusReset = await fetch(baseUrl + `/api/door/status?door=${ONBOARDING_DOOR_NAME}`).then(res => res.json()) as StatusResponse;

    // run auth with timeout specified
    const authResp = await fetch(baseUrl + `/api/door/auth?door=${ONBOARDING_DOOR_NAME}&key=${ONBOARDING_DOOR_PIN}`);

    // try to log call
    const logCallRes = await fetch(baseUrl + `/api/door/logCall?caller=${buzzerNumber}`);
    expect(logCallRes.status).toBe(200);

    const otp = (await logCallRes.json() as LogCallResponse).otp
    expect(otp.length).toBe(4);

    const checkOtpRes = await fetch(baseUrl + `/api/door/checkOtp?otp=${otp}`);

    expect(checkOtpRes.status).toBe(200);

    // check OTP 
    const caller = (await checkOtpRes.json() as CheckOtpResponse).buzzer;
    expect(caller).toBe(buzzerNumber);
  });

  test("call log after door closed, should not return OTP", async () => {
    // run status first, to make sure we are closed
    const statusReset = await fetch(baseUrl + `/api/door/status?door=${ONBOARDING_DOOR_NAME}`).then(res => res.json()) as StatusResponse;

    // run auth with timeout specified
    const authResp = await fetch(baseUrl + `/api/door/auth?door=${ONBOARDING_DOOR_NAME}&key=${ONBOARDING_DOOR_PIN}&timeout=1`);

    // sleep 1s
    await sleep(1_000);

    // try to log call
    const logCallRes = await fetch(baseUrl + `/api/door/logCall?caller=${buzzerNumber}`);
    expect(logCallRes.status).toBe(400);
  });
});
