import { afterEach, beforeEach, describe, expect, it } from 'bun:test';
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import * as zlib from 'zlib';
import { createEmptyUnifiedConfig } from '../../../../src/config/unified-config-types';
import { saveUnifiedConfig } from '../../../../src/config/unified-config-loader';
import { clearRecentLogEntries } from '../../../../src/services/logging/log-buffer';
import { invalidateLoggingConfigCache } from '../../../../src/services/logging/log-config';
import { getCurrentLogPath, getLogArchiveDir } from '../../../../src/services/logging/log-paths';
import {
  appendStructuredLogEntry,
  pruneExpiredLogArchives,
} from '../../../../src/services/logging/log-storage';
import type { LogEntry } from '../../../../src/services/logging/log-types';

function createEntry(overrides: Partial<LogEntry>): LogEntry {
  return {
    id: overrides.id ?? 'entry-1',
    timestamp: overrides.timestamp ?? new Date().toISOString(),
    level: overrides.level ?? 'info',
    source: overrides.source ?? 'unit:test',
    event: overrides.event ?? 'test.event',
    message: overrides.message ?? 'message',
    processId: overrides.processId ?? 1234,
    runId: overrides.runId ?? 'run-1',
    context: overrides.context ?? {},
  };
}

describe('log storage', () => {
  let tempHome = '';
  let originalCcsHome: string | undefined;

  beforeEach(() => {
    originalCcsHome = process.env.CCS_HOME;
    tempHome = fs.mkdtempSync(path.join(os.tmpdir(), 'ccs-log-storage-'));
    process.env.CCS_HOME = tempHome;
    clearRecentLogEntries();
    invalidateLoggingConfigCache();
  });

  afterEach(() => {
    if (originalCcsHome !== undefined) {
      process.env.CCS_HOME = originalCcsHome;
    } else {
      delete process.env.CCS_HOME;
    }

    clearRecentLogEntries();
    invalidateLoggingConfigCache();
    fs.rmSync(tempHome, { recursive: true, force: true });
    tempHome = '';
  });

  it('rotates the current log into the archive when the file exceeds the age threshold', () => {
    const config = createEmptyUnifiedConfig();
    config.logging.retain_days = 7;
    config.logging.rotate_mb = 10;
    saveUnifiedConfig(config);
    invalidateLoggingConfigCache();

    const currentLogPath = getCurrentLogPath();
    fs.mkdirSync(path.dirname(currentLogPath), { recursive: true });
    fs.writeFileSync(
      currentLogPath,
      `${JSON.stringify(createEntry({ id: 'old-entry' }))}\n`,
      'utf8'
    );
    const staleTimestamp = new Date(Date.now() - 2 * 24 * 60 * 60 * 1000);
    fs.utimesSync(currentLogPath, staleTimestamp, staleTimestamp);

    appendStructuredLogEntry(
      createEntry({
        id: 'new-entry',
        message: 'new log entry after rotation',
      })
    );

    const archiveDir = getLogArchiveDir();
    const archives = fs.readdirSync(archiveDir);
    expect(archives).toHaveLength(1);

    const archivedContent = zlib
      .gunzipSync(fs.readFileSync(path.join(archiveDir, archives[0])))
      .toString('utf8');
    expect(archivedContent).toContain('"id":"old-entry"');

    const currentContent = fs.readFileSync(currentLogPath, 'utf8');
    expect(currentContent).toContain('"id":"new-entry"');
    expect(currentContent).not.toContain('"id":"old-entry"');
  });

  it('prunes expired archives according to retention settings', () => {
    const config = createEmptyUnifiedConfig();
    config.logging.retain_days = 1;
    saveUnifiedConfig(config);
    invalidateLoggingConfigCache();

    const archiveDir = getLogArchiveDir();
    fs.mkdirSync(archiveDir, { recursive: true });

    const oldArchive = path.join(archiveDir, 'ccs-old.jsonl.gz');
    const freshArchive = path.join(archiveDir, 'ccs-fresh.jsonl.gz');
    fs.writeFileSync(oldArchive, zlib.gzipSync('old archive'), { mode: 0o600 });
    fs.writeFileSync(freshArchive, zlib.gzipSync('fresh archive'), { mode: 0o600 });

    const oldTimestamp = new Date(Date.now() - 3 * 24 * 60 * 60 * 1000);
    const freshTimestamp = new Date(Date.now() - 2 * 60 * 60 * 1000);
    fs.utimesSync(oldArchive, oldTimestamp, oldTimestamp);
    fs.utimesSync(freshArchive, freshTimestamp, freshTimestamp);

    pruneExpiredLogArchives();

    expect(fs.existsSync(oldArchive)).toBe(false);
    expect(fs.existsSync(freshArchive)).toBe(true);
  });
});
