import '@angular/compiler';
import { Injector, runInInjectionContext } from '@angular/core';
import {
  beforeEach,
  describe,
  expect,
  it,
  vi
} from 'vitest';

import { PlatformService } from '../../core/platform';
import { BrowserDatabaseService } from './browser-database.service';
import { CapacitorDatabaseService } from './capacitor-database.service';
import { DatabaseService } from './database.service';
import { ElectronDatabaseService } from './electron-database.service';

function installLocalStorageMock(): void {
  const store = new Map<string, string>();

  vi.stubGlobal('localStorage', {
    getItem: (key: string) => store.get(key) ?? null,
    setItem: (key: string, value: string) => store.set(key, String(value)),
    removeItem: (key: string) => store.delete(key),
    clear: () => store.clear(),
    key: (index: number) => Array.from(store.keys())[index] ?? null,
    get length() {
      return store.size;
    }
  });
}

describe('DatabaseService', () => {
  let browserDatabase: {
    getBansForRoom: ReturnType<typeof vi.fn>;
    initialize: ReturnType<typeof vi.fn>;
  };
  let capacitorDatabase: {
    getBansForRoom: ReturnType<typeof vi.fn>;
    initialize: ReturnType<typeof vi.fn>;
  };
  let electronDatabase: {
    getBansForRoom: ReturnType<typeof vi.fn>;
    initialize: ReturnType<typeof vi.fn>;
  };

  beforeEach(() => {
    installLocalStorageMock();
    localStorage.clear();

    browserDatabase = {
      getBansForRoom: vi.fn(() => Promise.resolve([])),
      initialize: vi.fn(() => Promise.resolve())
    };

    capacitorDatabase = {
      getBansForRoom: vi.fn(() => Promise.resolve([])),
      initialize: vi.fn(() => Promise.resolve())
    };

    electronDatabase = {
      getBansForRoom: vi.fn(() => Promise.resolve([])),
      initialize: vi.fn(() => Promise.resolve())
    };
  });

  function createService(platform: Pick<PlatformService, 'isBrowser' | 'isElectron' | 'isCapacitor'>): DatabaseService {
    const injector = Injector.create({
      providers: [
        DatabaseService,
        { provide: PlatformService, useValue: platform },
        { provide: BrowserDatabaseService, useValue: browserDatabase },
        { provide: CapacitorDatabaseService, useValue: capacitorDatabase },
        { provide: ElectronDatabaseService, useValue: electronDatabase }
      ]
    });

    return runInInjectionContext(injector, () => injector.get(DatabaseService));
  }

  it('initializes the browser backend before the first delegated read', async () => {
    const service = createService({ isBrowser: true, isElectron: false, isCapacitor: false });

    await service.getBansForRoom('room-1');

    expect(browserDatabase.initialize).toHaveBeenCalledTimes(1);
    expect(browserDatabase.getBansForRoom).toHaveBeenCalledWith('room-1');
    expect(capacitorDatabase.initialize).not.toHaveBeenCalled();
    expect(service.isReady()).toBe(true);
  });

  it('rechecks backend initialization when the user scope changes during an in-flight initialize call', async () => {
    let finishInitialInitialize!: () => void;

    browserDatabase.initialize = vi.fn()
      .mockImplementationOnce(() => new Promise<void>((resolve) => {
        finishInitialInitialize = resolve;
      }))
      .mockResolvedValue(undefined);

    localStorage.setItem('metoyou_currentUserId', 'user-a');

    const service = createService({ isBrowser: true, isElectron: false, isCapacitor: false });
    const initialInitialize = service.initialize();

    localStorage.setItem('metoyou_currentUserId', 'user-b');
    const initializeAfterScopeChange = service.initialize();

    expect(browserDatabase.initialize).toHaveBeenCalledTimes(1);

    finishInitialInitialize();
    await Promise.all([initialInitialize, initializeAfterScopeChange]);

    expect(browserDatabase.initialize).toHaveBeenCalledTimes(2);
    expect(service.isReady()).toBe(true);
  });

  it('does not reinitialize the browser backend for repeated reads in the same user scope', async () => {
    const service = createService({ isBrowser: true, isElectron: false, isCapacitor: false });

    await service.getBansForRoom('room-1');
    await service.getBansForRoom('room-2');

    expect(browserDatabase.initialize).toHaveBeenCalledTimes(1);
    expect(browserDatabase.getBansForRoom).toHaveBeenCalledWith('room-1');
    expect(browserDatabase.getBansForRoom).toHaveBeenCalledWith('room-2');
  });

  it('reinitializes the browser backend when the stored user scope changes', async () => {
    localStorage.setItem('metoyou_currentUserId', 'user-a');

    const service = createService({ isBrowser: true, isElectron: false, isCapacitor: false });

    await service.getBansForRoom('room-1');
    localStorage.setItem('metoyou_currentUserId', 'user-b');
    await service.getBansForRoom('room-2');

    expect(browserDatabase.initialize).toHaveBeenCalledTimes(2);
    expect(browserDatabase.getBansForRoom).toHaveBeenCalledWith('room-2');
  });

  it('routes Capacitor shells to native SQLite instead of IndexedDB', async () => {
    const service = createService({ isBrowser: false, isElectron: false, isCapacitor: true });

    await service.getBansForRoom('room-1');

    expect(capacitorDatabase.initialize).toHaveBeenCalledTimes(1);
    expect(capacitorDatabase.getBansForRoom).toHaveBeenCalledWith('room-1');
    expect(browserDatabase.initialize).not.toHaveBeenCalled();
  });

  it('routes Electron shells to the IPC SQLite backend', async () => {
    const service = createService({ isBrowser: false, isElectron: true, isCapacitor: false });

    await service.getBansForRoom('room-1');

    expect(electronDatabase.initialize).toHaveBeenCalledTimes(1);
    expect(electronDatabase.getBansForRoom).toHaveBeenCalledWith('room-1');
    expect(browserDatabase.initialize).not.toHaveBeenCalled();
    expect(capacitorDatabase.initialize).not.toHaveBeenCalled();
  });
});
