import { describe, expect, test } from "vitest";

import {
  mockTimestampToDate,
  mockTimestampToMillis,
  mockTimestampNow,
  Timestamp,
} from "../mocks/firestore";
import * as admin from "firebase-admin";
const ref = admin.firestore.Timestamp;

describe("Timestamp mock", () => {
  test("it is equal to itself", () => {
    const timestamp = new Timestamp(500, 20);
    expect(timestamp.isEqual(timestamp)).toBe(true);
  });

  test("it is equal to a separate instance", () => {
    const timestamp = new Timestamp(500, 20);
    const other = new Timestamp(500, 20);
    expect(timestamp.isEqual(other)).toBe(true);
  });

  test("it is not equal to an instance whose properties differ", () => {
    const timestamp = new Timestamp(500, 20);
    const diffSeconds = new Timestamp(550, 20);
    const diffNano = new Timestamp(500, 40);
    const diffAll = new Timestamp(550, 40);
    expect(timestamp.isEqual(diffSeconds)).toBe(false);
    expect(timestamp.isEqual(diffNano)).toBe(false);
    expect(timestamp.isEqual(diffAll)).toBe(false);
  });

  test("it converts itself roughly to a Date representation", () => {
    // Since this is a mock (and I'm bad with time maths) we don't expect nanosecond accuracy
    const timestamp = new Timestamp(40, 0);
    expect(timestamp.toDate()).toBeInstanceOf(Date);
    expect(timestamp.toDate().getSeconds()).toBe(40);
    expect(mockTimestampToDate).toHaveBeenCalled();
  });

  test("it allows clients to override the Date representation", () => {
    const timestamp = new Timestamp(40, 0);
    const now = new Date();
    mockTimestampToDate.mockReturnValueOnce(now);
    expect(timestamp.toDate()).toBe(now);
    expect(timestamp.toDate().getSeconds()).toBe(40); // second call should be the original
  });

  test("it converts itself roughly to millisecond representation", () => {
    // The mock only returns 0, but it calls mockTimestampToMillis first, returning its result if defined
    const timestamp = new Timestamp(40, 80);
    const now = new Date();
    mockTimestampToMillis.mockReturnValueOnce(now.getMilliseconds());
    expect(timestamp.toMillis()).toBe(now.getMilliseconds());
    expect(timestamp.toMillis()).toBe(40000); // second call should be the original (40s)
    expect(mockTimestampToMillis).toHaveBeenCalledTimes(2);
  });

  test("it creates an instance roughly from a Date representation", () => {
    const now = new Date();
    const timestamp = Timestamp.fromDate(now);
    expect(timestamp).toBeDefined();
    expect(timestamp).toBeInstanceOf(Timestamp);
    expect(timestamp.toDate().getTime()).toBe(now.getTime());
  });

  test("it creates an instance roughly from a millisecond representation", () => {
    const date = new Date(0);
    date.setMilliseconds(54000);
    const timestamp = Timestamp.fromMillis(54000);
    expect(timestamp).toBeDefined();
    expect(timestamp).toBeInstanceOf(Timestamp);
    expect(timestamp.seconds).toBe(date.getSeconds());
  });

  test("Timestamp.now reports calls to mockTimestampNow", () => {
    expect(mockTimestampNow).not.toHaveBeenCalled();
    const timestamp = Timestamp.now();
    expect(timestamp).toBeInstanceOf(Timestamp);
    expect(mockTimestampNow).toHaveBeenCalled();
  });

  test("Timestamp.now can be mocked", () => {
    mockTimestampNow.mockReturnValueOnce("Success!");
    const timestamp = Timestamp.now();
    expect(timestamp).toBe("Success!");
  });

  test("it handles negative seconds", () => {
    // Since this is a mock (and I'm bad with time maths) we don't expect nanosecond accuracy
    const timestamp = Timestamp.fromMillis(-54001);
    const rts = ref.fromMillis(-54001);
    expect(timestamp.seconds).toBe(rts.seconds);
    expect(timestamp.nanoseconds).toBe(rts.nanoseconds);
  });
});
