/**
 * Tests unitaires pour BackupService
 */

/* eslint-disable @typescript-eslint/no-explicit-any */
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { BackupService } from '../backup';
import { prisma } from '@/services/core/database';
import { userPreferencesService } from '@/services/core/user-preferences';
import { BackupUtils } from '@/lib/backup-utils';
import { promises as fs } from 'fs';
import path from 'path';

// Mock de prisma
vi.mock('@/services/core/database', () => ({
  prisma: {
    $disconnect: vi.fn(),
    $connect: vi.fn(),
    $queryRaw: vi.fn(),
    userPreferences: {
      upsert: vi.fn(),
    },
  },
}));

// Mock de userPreferencesService
vi.mock('@/services/core/user-preferences', () => ({
  userPreferencesService: {
    getAllPreferences: vi.fn(),
    getViewPreferences: vi.fn(),
  },
}));

// Mock de BackupUtils
vi.mock('@/lib/backup-utils', () => ({
  BackupUtils: {
    resolveBackupStoragePath: vi.fn(),
    resolveDatabasePath: vi.fn(),
    calculateFileHash: vi.fn(),
    ensureDirectory: vi.fn(),
    createSQLiteBackup: vi.fn(),
    compressFile: vi.fn(),
    decompressFileTemp: vi.fn(),
    writeLogEntry: vi.fn(),
    parseBackupFilename: vi.fn(),
    generateBackupFilename: vi.fn(),
  },
}));

// Mock de fs (pour promises)
vi.mock('fs', () => ({
  promises: {
    readFile: vi.fn(),
    writeFile: vi.fn(),
    stat: vi.fn(),
    readdir: vi.fn(),
    unlink: vi.fn(),
    access: vi.fn(),
    copyFile: vi.fn(),
  },
}));

describe('BackupService', () => {
  let backupService: BackupService;

  beforeEach(() => {
    vi.clearAllMocks();
    backupService = new BackupService();

    // Mocks par défaut
    vi.mocked(BackupUtils.resolveBackupStoragePath).mockReturnValue('/backups');
    vi.mocked(BackupUtils.resolveDatabasePath).mockReturnValue('/db/dev.db');
    vi.mocked(BackupUtils.calculateFileHash).mockResolvedValue('hash123');
    vi.mocked(BackupUtils.ensureDirectory).mockResolvedValue(undefined);
    vi.mocked(BackupUtils.createSQLiteBackup).mockResolvedValue(undefined);
    vi.mocked(BackupUtils.compressFile).mockResolvedValue(
      '/backups/file.db.gz'
    );
    vi.mocked(BackupUtils.parseBackupFilename).mockReturnValue({
      type: 'manual',
      date: new Date(),
    });
    vi.mocked(BackupUtils.generateBackupFilename).mockReturnValue(
      'towercontrol_manual_2024-01-01.db'
    );
    vi.mocked(fs.stat).mockResolvedValue({
      size: 1024,
      birthtime: new Date(),
    } as any);
    vi.mocked(fs.readdir).mockResolvedValue([]);
    vi.mocked(userPreferencesService.getAllPreferences).mockResolvedValue({
      viewPreferences: {},
    } as any);
    vi.mocked(userPreferencesService.getViewPreferences).mockResolvedValue(
      {} as any
    );
  });

  describe('createBackup', () => {
    it('devrait créer une sauvegarde manuelle', async () => {
      vi.mocked(BackupUtils.compressFile).mockResolvedValue(
        '/backups/towercontrol_manual_2024-01-01.db.gz'
      );
      vi.mocked(fs.stat).mockResolvedValue({
        size: 2048,
        birthtime: new Date(),
      } as any);

      const result = await backupService.createBackup('manual', true);

      expect(result).not.toBeNull();
      expect(result?.type).toBe('manual');
      expect(result?.status).toBe('success');
      expect(result?.filename).toContain('towercontrol_manual');
    });

    it('devrait créer une sauvegarde automatique', async () => {
      vi.mocked(BackupUtils.compressFile).mockResolvedValue(
        '/backups/towercontrol_automatic_2024-01-01.db.gz'
      );

      const result = await backupService.createBackup('automatic', true);

      expect(result).not.toBeNull();
      expect(result?.type).toBe('automatic');
    });

    it('devrait retourner null si pas de changements et forceCreate=false', async () => {
      // Mock pour simuler qu'il n'y a pas de changements
      vi.mocked(fs.readdir).mockResolvedValue([
        'towercontrol_manual_2024-01-01.db.gz',
      ] as any);
      vi.mocked(BackupUtils.calculateFileHash).mockResolvedValue('samehash');
      vi.mocked(fs.readFile).mockResolvedValue(
        JSON.stringify({ databaseHash: 'samehash' })
      );

      const result = await backupService.createBackup('automatic', false);

      // Si pas de changements, devrait retourner null
      expect(result).toBeNull();
    });

    it('devrait gérer les erreurs lors de la création', async () => {
      vi.mocked(BackupUtils.createSQLiteBackup).mockRejectedValue(
        new Error('Backup failed')
      );

      const result = await backupService.createBackup('manual', true);

      expect(result).not.toBeNull();
      expect(result?.status).toBe('failed');
      expect(result?.error).toBeDefined();
    });
  });

  describe('listBackups', () => {
    it('devrait lister les sauvegardes disponibles', async () => {
      vi.mocked(fs.readdir).mockResolvedValue([
        'towercontrol_manual_2024-01-01.db.gz',
        'towercontrol_automatic_2024-01-02.db.gz',
      ] as any);

      const backups = await backupService.listBackups();

      expect(backups).toHaveLength(2);
      expect(backups[0].filename).toContain('towercontrol_');
    });

    it('devrait retourner une liste vide si aucun backup', async () => {
      vi.mocked(fs.readdir).mockResolvedValue([]);

      const backups = await backupService.listBackups();

      expect(backups).toHaveLength(0);
    });

    it('devrait filtrer les fichiers non-backup', async () => {
      vi.mocked(fs.readdir).mockResolvedValue([
        'towercontrol_manual_2024-01-01.db.gz',
        'other_file.txt',
        'backup.log',
      ] as any);

      const backups = await backupService.listBackups();

      expect(backups).toHaveLength(1);
    });

    it('devrait gérer les erreurs', async () => {
      (fs.readdir as any).mockRejectedValue(new Error('Read error'));

      const backups = await backupService.listBackups();

      expect(backups).toHaveLength(0);
    });
  });

  describe('deleteBackup', () => {
    it('devrait supprimer un backup et ses métadonnées', async () => {
      await backupService.deleteBackup('backup.db.gz');

      expect(fs.unlink).toHaveBeenCalledWith(
        path.join('/backups', 'backup.db.gz')
      );
      expect(fs.unlink).toHaveBeenCalledWith(
        path.join('/backups', 'backup.db.gz.meta.json')
      );
    });

    it('devrait gérer les erreurs lors de la suppression', async () => {
      vi.mocked(fs.unlink).mockRejectedValue(new Error('Delete failed'));

      await expect(
        backupService.deleteBackup('backup.db.gz')
      ).rejects.toThrow();
    });
  });

  describe('restoreBackup', () => {
    beforeEach(() => {
      vi.mocked(prisma.$queryRaw).mockResolvedValue([
        { integrity_check: 'ok' },
      ]);
      vi.spyOn(backupService, 'createBackup').mockResolvedValue({
        id: 'pre-restore',
        filename: 'pre-restore.db.gz',
        size: 1024,
        createdAt: new Date(),
        type: 'manual',
        status: 'success',
      } as any);
    });

    it('devrait restaurer un backup non compressé', async () => {
      vi.mocked(fs.access).mockResolvedValue(undefined);
      vi.mocked(fs.copyFile).mockResolvedValue(undefined);

      await backupService.restoreBackup('backup.db');

      expect(fs.copyFile).toHaveBeenCalled();
      expect(prisma.$connect).toHaveBeenCalled();
    });

    it('devrait décompresser et restaurer un backup compressé', async () => {
      vi.mocked(fs.access).mockResolvedValue(undefined);
      vi.mocked(BackupUtils.decompressFileTemp).mockResolvedValue(undefined);
      vi.mocked(fs.copyFile).mockResolvedValue(undefined);
      vi.mocked(fs.unlink).mockResolvedValue(undefined);

      await backupService.restoreBackup('backup.db.gz');

      expect(BackupUtils.decompressFileTemp).toHaveBeenCalled();
      expect(fs.copyFile).toHaveBeenCalled();
    });

    it('devrait gérer les erreurs lors de la restauration', async () => {
      vi.mocked(fs.access).mockRejectedValue(new Error('File not found'));

      await expect(backupService.restoreBackup('backup.db')).rejects.toThrow();
    });
  });

  describe('verifyDatabaseHealth', () => {
    it("devrait vérifier l'intégrité de la base de données", async () => {
      vi.mocked(prisma.$queryRaw).mockResolvedValue([
        { integrity_check: 'ok' },
      ]);

      await backupService.verifyDatabaseHealth();

      expect(prisma.$queryRaw).toHaveBeenCalled();
    });

    it("devrait échouer si l'intégrité est compromise", async () => {
      vi.mocked(prisma.$queryRaw).mockResolvedValue([
        { integrity_check: 'error in database' },
      ]);

      await expect(backupService.verifyDatabaseHealth()).rejects.toThrow();
    });
  });

  describe('hasChangedSinceLastBackup', () => {
    it('devrait retourner true si aucun backup précédent', async () => {
      vi.mocked(fs.readdir).mockResolvedValue([]);

      const hasChanged = await backupService.hasChangedSinceLastBackup();

      expect(hasChanged).toBe(true);
    });

    it('devrait retourner true si le hash a changé', async () => {
      vi.mocked(fs.readdir).mockResolvedValue([
        'towercontrol_manual_2024-01-01.db.gz',
      ] as any);
      vi.mocked(BackupUtils.calculateFileHash)
        .mockResolvedValueOnce('newhash')
        .mockResolvedValueOnce('oldhash');
      vi.mocked(fs.readFile).mockResolvedValue(
        JSON.stringify({ databaseHash: 'oldhash' })
      );

      const hasChanged = await backupService.hasChangedSinceLastBackup();

      expect(hasChanged).toBe(true);
    });
  });

  describe('getBackupLogs', () => {
    it('devrait lire les logs de backup', async () => {
      vi.mocked(fs.readFile).mockResolvedValue(
        '2024-01-01: Backup created\n2024-01-02: Backup created'
      );

      const logs = await backupService.getBackupLogs();

      expect(logs).toHaveLength(2);
    });

    it("devrait retourner une liste vide si le fichier n'existe pas", async () => {
      (fs.readFile as any).mockRejectedValue(new Error('File not found'));

      const logs = await backupService.getBackupLogs();

      expect(logs).toHaveLength(0);
    });

    it('devrait limiter le nombre de lignes', async () => {
      const manyLines = Array.from({ length: 200 }, (_, i) => `Line ${i}`).join(
        '\n'
      );
      vi.mocked(fs.readFile).mockResolvedValue(manyLines);

      const logs = await backupService.getBackupLogs(50);

      expect(logs.length).toBeLessThanOrEqual(50);
    });
  });

  describe('getBackupStats', () => {
    it('devrait calculer les statistiques par jour', async () => {
      const now = new Date('2024-01-15');
      vi.useFakeTimers();
      vi.setSystemTime(now);

      vi.mocked(fs.readdir).mockResolvedValue([
        'towercontrol_manual_2024-01-15.db.gz',
        'towercontrol_automatic_2024-01-15.db.gz',
        'towercontrol_manual_2024-01-14.db.gz',
      ] as any);
      vi.mocked(BackupUtils.parseBackupFilename)
        .mockReturnValueOnce({
          type: 'manual',
          date: new Date('2024-01-15'),
        })
        .mockReturnValueOnce({
          type: 'automatic',
          date: new Date('2024-01-15'),
        })
        .mockReturnValueOnce({
          type: 'manual',
          date: new Date('2024-01-14'),
        });

      const stats = await backupService.getBackupStats(7);

      expect(stats.length).toBeGreaterThan(0);
      const todayStats = stats.find((s) => s.date === '2024-01-15');
      expect(todayStats).toBeDefined();
      expect(todayStats?.manual).toBe(1);
      expect(todayStats?.automatic).toBe(1);

      vi.useRealTimers();
    });
  });

  describe('getConfig', () => {
    it('devrait retourner la configuration', async () => {
      const config = await backupService.getConfig();

      expect(config).toHaveProperty('enabled');
      expect(config).toHaveProperty('interval');
      expect(config).toHaveProperty('maxBackups');
      expect(config).toHaveProperty('backupPath');
    });
  });

  describe('updateConfig', () => {
    it('devrait mettre à jour la configuration', async () => {
      vi.mocked(prisma.userPreferences.upsert).mockResolvedValue({
        userId: 'default',
      } as any);

      await backupService.updateConfig({ maxBackups: 10 });

      expect(prisma.userPreferences.upsert).toHaveBeenCalled();
    });
  });
});
