import { createApp } from 'vue';

import { defineStore } from 'pinia';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';

import { useAccessStore } from '../modules/access';
import { initStores, withSessionAuthLogin } from '../setup';

const TestApp = {};

const usePreferences = defineStore('test-preferences', {
  state: () => ({ theme: 'light' }),
  persist: true,
});

function memoryStorage(): Storage {
  const data = new Map<string, string>();
  return {
    get length() {
      return data.size;
    },
    clear: () => data.clear(),
    getItem: (key) => data.get(key) ?? null,
    key: (index) => [...data.keys()][index] ?? null,
    removeItem: (key) => {
      data.delete(key);
    },
    setItem: (key, value) => {
      data.set(key, value);
    },
  };
}

beforeEach(() => {
  vi.stubGlobal('localStorage', memoryStorage());
  vi.stubGlobal('sessionStorage', memoryStorage());
});

afterEach(() => {
  vi.unstubAllEnvs();
  localStorage.clear();
  sessionStorage.clear();
  vi.unstubAllGlobals();
});

describe.each([true, false])('pinia session auth (development=%s)', (development) => {
  it('hydrates real access store from session storage and preserves unrelated persistence', async () => {
    vi.stubEnv('DEV', development);
    vi.stubGlobal('BroadcastChannel', undefined);
    const app = createApp(TestApp);
    const pinia = await initStores(app, { namespace: 'test', sessionAuth: true });
    const access = useAccessStore(pinia);
    access.setAccessToken('only-session-token');
    access.setRefreshToken('only-session-refresh');
    access.$persist();
    const preferences = usePreferences(pinia);
    preferences.theme = 'dark';
    preferences.$persist();
    expect(localStorage.getItem('test-core-access')).toBeNull();
    expect(localStorage.getItem('test-test-preferences')).toBeTruthy();
    const persisted = Array.from({ length: localStorage.length }, (_, index) => localStorage.getItem(localStorage.key(index)!)).join('');
    expect(persisted).not.toContain('only-session-token');
    const saved = sessionStorage.getItem('test-core-access-session');
    expect(saved).toBeTruthy();
    if (!development) expect(saved).not.toContain('only-session-token');

    const refreshed = await initStores(createApp(TestApp), { namespace: 'test', sessionAuth: true });
    expect(useAccessStore(refreshed).accessToken).toBe('only-session-token');
    expect(useAccessStore(refreshed).refreshToken).toBe('only-session-refresh');
    expect(usePreferences(refreshed).theme).toBe('dark');

    sessionStorage.clear();
    const reopened = await initStores(createApp(TestApp), { namespace: 'test', sessionAuth: true });
    expect(useAccessStore(reopened).accessToken).toBeNull();
    expect(usePreferences(reopened).theme).toBe('dark');
  });

  it('preserves shared sub-application token scope', async () => {
    vi.stubEnv('DEV', development);
    vi.stubGlobal('BroadcastChannel', undefined);
    const first = await initStores(createApp(TestApp), { namespace: 'test_first', isCommonToken: true, sessionAuth: true });
    const access = useAccessStore(first);
    access.setAccessToken('shared-app-token');
    access.$persist();
    const second = await initStores(createApp(TestApp), { namespace: 'test_second', isCommonToken: true, sessionAuth: true });
    expect(useAccessStore(second).accessToken).toBe('shared-app-token');
  });

  it('does not change applications that do not opt in', async () => {
    vi.stubEnv('DEV', development);
    const pinia = await initStores(createApp(TestApp), { namespace: 'legacy' });
    const access = useAccessStore(pinia);
    access.setAccessToken('persistent-token');
    access.$persist();
    expect(localStorage.getItem('legacy-core-access')).toBeTruthy();
    expect(sessionStorage.length).toBe(0);
  });
});

it('serializes competing password submissions and reuses the completed session', async () => {
  vi.stubGlobal('BroadcastChannel', undefined);
  let queue = Promise.resolve();
  vi.stubGlobal('navigator', {
    locks: {
      request: (_name: string, run: () => Promise<void>) => {
        const result = queue.then(run);
        queue = result.catch(() => {});
        return result;
      },
    },
  });
  const pinia = await initStores(createApp(TestApp), { namespace: 'mutex', sessionAuth: true });
  const access = useAccessStore(pinia);
  const login = vi.fn(async (token: null | string) => {
    if (token) return 'reused';
    access.setAccessToken('one-login');
    access.$persist();
    return 'created';
  });
  expect(await Promise.all([withSessionAuthLogin(login), withSessionAuthLogin(login)])).toEqual(['created', 'reused']);
  expect(login.mock.calls.map(([token]) => token)).toEqual([null, 'one-login']);
});
