import { describe, it, expect, beforeEach, vi } from "vitest";
import { createStorageService } from "../createStorageService";

// ---------------------------------------------------------------------------
// localStorage mock
// ---------------------------------------------------------------------------
const store = {};
const localStorageMock = {
  getItem: vi.fn((k) => store[k] ?? null),
  setItem: vi.fn((k, v) => { store[k] = v; }),
  removeItem: vi.fn((k) => { delete store[k]; }),
};
vi.stubGlobal("localStorage", localStorageMock);

beforeEach(() => {
  Object.keys(store).forEach((k) => delete store[k]);
  vi.clearAllMocks();
});

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
const isString = (v) => typeof v === "string";
const isBlank = (v) => v.trim() === "";

// ---------------------------------------------------------------------------
describe("createStorageService — load()", () => {
  it("devolve null quando a chave não existe", () => {
    const svc = createStorageService({ validator: isString });
    expect(svc.load("missing")).toBeNull();
  });

  it("devolve o valor quando válido", () => {
    store["k"] = JSON.stringify("hello");
    const svc = createStorageService({ validator: isString });
    expect(svc.load("k")).toBe("hello");
  });

  it("devolve null e remove a chave quando o valor não passa no validator", () => {
    store["k"] = JSON.stringify(42); // não é string
    const svc = createStorageService({ validator: isString });
    expect(svc.load("k")).toBeNull();
  });

  it("devolve null e remove a chave quando o JSON está corrompido", () => {
    store["k"] = "{ invalid json";
    const svc = createStorageService({ validator: isString });
    expect(svc.load("k")).toBeNull();
    expect(localStorageMock.removeItem).toHaveBeenCalledWith("k");
  });
});

describe("createStorageService — save()", () => {
  it("persiste um valor válido via JSON.stringify", () => {
    const svc = createStorageService({ validator: isString });
    svc.save("k", "world");
    expect(localStorageMock.setItem).toHaveBeenCalledWith("k", JSON.stringify("world"));
  });

  it("remove a chave quando o validator rejeita o valor", () => {
    const svc = createStorageService({ validator: isString });
    svc.save("k", 99); // não é string
    expect(localStorageMock.removeItem).toHaveBeenCalledWith("k");
    expect(localStorageMock.setItem).not.toHaveBeenCalled();
  });

  it("remove a chave quando isEmpty retorna true", () => {
    const svc = createStorageService({ validator: isString, isEmpty: isBlank });
    svc.save("k", "   ");
    expect(localStorageMock.removeItem).toHaveBeenCalledWith("k");
    expect(localStorageMock.setItem).not.toHaveBeenCalled();
  });

  it("persiste quando valor é válido e não está vazio", () => {
    const svc = createStorageService({ validator: isString, isEmpty: isBlank });
    svc.save("k", "código");
    expect(localStorageMock.setItem).toHaveBeenCalledWith("k", JSON.stringify("código"));
  });

  it("usa isEmpty=()=>false por padrão (não remove valores válidos)", () => {
    const svc = createStorageService({ validator: isString }); // sem isEmpty
    svc.save("k", "");  // string vazia mas sem isEmpty configurado
    expect(localStorageMock.setItem).toHaveBeenCalledWith("k", JSON.stringify(""));
  });
});

describe("createStorageService — clear()", () => {
  it("remove a chave do localStorage", () => {
    store["k"] = "qualquer";
    const svc = createStorageService({ validator: isString });
    svc.clear("k");
    expect(localStorageMock.removeItem).toHaveBeenCalledWith("k");
  });
});

describe("createStorageService — createDebouncedSave()", () => {
  it("retorna uma função", () => {
    const svc = createStorageService({ validator: isString });
    expect(typeof svc.createDebouncedSave()).toBe("function");
  });

  it("instâncias diferentes são funções independentes", () => {
    const svc = createStorageService({ validator: isString });
    const fn1 = svc.createDebouncedSave(500);
    const fn2 = svc.createDebouncedSave(500);
    expect(fn1).not.toBe(fn2);
  });
});
