import { Test, TestingModule } from '@nestjs/testing';
import Database from 'better-sqlite3';
import { ConfigService } from './config.service';

// Mock better-sqlite3
jest.mock('better-sqlite3', () => {
  const mockDb = {
    exec: jest.fn(),
    prepare: jest.fn().mockReturnValue({
      run: jest.fn().mockReturnValue({}),
      get: jest.fn(),
      all: jest.fn(),
    }),
    close: jest.fn(),
  };
  return jest.fn(() => mockDb);
});

describe('ConfigService', () => {
  let service: ConfigService;
  let mockDb: any;

  beforeEach(async () => {
    jest.clearAllMocks();

    const module: TestingModule = await Test.createTestingModule({
      providers: [ConfigService],
    }).compile();

    service = module.get<ConfigService>(ConfigService);
    mockDb = (Database as jest.MockedFunction<typeof Database>).mock.results[0]
      ?.value;
  });

  it('should be defined', () => {
    expect(service).toBeDefined();
  });

  describe('constructor', () => {
    it('should create database and table on initialization', () => {
      // Find project root by traversing up from current directory until we find the root package.json
      let projectRoot = process.cwd();
      while (projectRoot !== path.dirname(projectRoot)) {
        if (
          require('fs').existsSync(
            require('path').join(projectRoot, 'package.json'),
          )
        ) {
          try {
            const pkg = JSON.parse(
              require('fs').readFileSync(
                require('path').join(projectRoot, 'package.json'),
                'utf-8',
              ),
            );
            if (pkg.name === 'watch-finished-turbo') {
              break;
            }
          } catch (e) {
            // ignore
          }
        }
        projectRoot = require('path').dirname(projectRoot);
      }

      expect(Database).toHaveBeenCalledWith(
        require('path').resolve(projectRoot, 'data', 'database.db'),
      );
      expect(mockDb.exec).toHaveBeenCalledWith(
        expect.stringContaining('CREATE TABLE IF NOT EXISTS settings'),
      );
      expect(mockDb.close).toHaveBeenCalled();
    });
  });

  describe('getSettings', () => {
    beforeEach(() => {
      // Reset the mock for each test
      mockDb.prepare.mockClear();
      mockDb.close.mockClear();
    });

    it('should return all settings when no key provided', () => {
      const mockAll = mockDb.prepare().all;
      mockAll.mockReturnValue([
        { key: 'testKey', value: JSON.stringify('testValue') },
      ]);

      const result = service.getSettings();

      expect(mockDb.prepare).toHaveBeenCalledWith(
        'SELECT key, value FROM settings',
      );
      expect(result).toEqual({ testKey: 'testValue' });
      expect(mockDb.close).toHaveBeenCalled();
    });

    it('should return specific setting when key provided', () => {
      const mockGet = mockDb.prepare().get;
      mockGet.mockReturnValue({ value: JSON.stringify('testValue') });

      const result = service.getSettings('testKey');

      expect(mockDb.prepare).toHaveBeenCalledWith(
        'SELECT value FROM settings WHERE key = ?',
      );
      expect(result).toEqual('testValue');
      expect(mockDb.close).toHaveBeenCalled();
    });

    it('should return default value when key not found', () => {
      const mockGet = mockDb.prepare().get;
      mockGet.mockReturnValue(undefined);

      const result = service.getSettings('missingKey', 'defaultValue');

      expect(result).toEqual('defaultValue');
      expect(mockDb.close).toHaveBeenCalled();
    });
  });

  describe('setSettings', () => {
    beforeEach(() => {
      mockDb.prepare.mockClear();
      mockDb.close.mockClear();
    });

    it('should insert settings successfully', () => {
      const settings = { key1: 'value1', key2: 'value2' };

      const result = service.setSettings(settings);

      expect(Database).toHaveBeenCalledTimes(2); // Constructor + setSettings
      expect(mockDb.prepare).toHaveBeenCalledWith(
        'INSERT OR REPLACE INTO settings (key, value) VALUES (?, ?)',
      );
      const mockRun = mockDb.prepare().run;
      expect(mockRun).toHaveBeenCalledTimes(2);
      expect(mockRun).toHaveBeenCalledWith('key1', JSON.stringify('value1'));
      expect(mockRun).toHaveBeenCalledWith('key2', JSON.stringify('value2'));
      expect(mockDb.close).toHaveBeenCalled();
      expect(result).toBe(true);
    });

    it('should return false on database error', () => {
      const settings = { key1: 'value1' };
      const mockRun = mockDb.prepare().run;
      mockRun.mockImplementation(() => {
        throw new Error('Database error');
      });

      const result = service.setSettings(settings);

      expect(result).toBe(false);
    });
  });

  describe('deleteSetting', () => {
    beforeEach(() => {
      mockDb.prepare.mockClear();
      mockDb.close.mockClear();
    });

    it('should delete setting successfully', () => {
      const mockRun = mockDb.prepare().run;
      mockRun.mockReturnValue({ changes: 1 });

      const result = service.deleteSetting('testKey');

      expect(mockDb.prepare).toHaveBeenCalledWith(
        'DELETE FROM settings WHERE key = ?',
      );
      expect(mockRun).toHaveBeenCalledWith('testKey');
      expect(mockDb.close).toHaveBeenCalled();
      expect(result).toBe(true);
    });

    it('should return false on database error', () => {
      const mockRun = mockDb.prepare().run;
      mockRun.mockImplementation(() => {
        throw new Error('Database error');
      });

      const result = service.deleteSetting('testKey');

      expect(result).toBe(false);
    });
  });
});
