/**
 * Cursor Settings Routes Tests
 * Tests for Cursor configuration API endpoints.
 */

import { describe, it, expect, beforeEach, afterEach } from 'bun:test';
import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';

// Setup test environment BEFORE any imports
const TEST_CCS_DIR = path.join(os.tmpdir(), `ccs-test-cursor-settings-${Date.now()}`);
process.env.CCS_HOME = TEST_CCS_DIR;

// Import after setting env var
import type { CursorConfig } from '../../../src/config/unified-config-types';
import {
  loadOrCreateUnifiedConfig,
  saveUnifiedConfig,
} from '../../../src/config/unified-config-loader';
import { getCcsDir } from '../../../src/utils/config-manager';

describe('Cursor Settings Routes Logic', () => {
  beforeEach(() => {
    // Ensure test directory exists
    const ccsDir = getCcsDir();
    if (!fs.existsSync(ccsDir)) {
      fs.mkdirSync(ccsDir, { recursive: true });
    }
  });

  afterEach(() => {
    // Clean up test directory
    if (fs.existsSync(TEST_CCS_DIR)) {
      fs.rmSync(TEST_CCS_DIR, { recursive: true, force: true });
    }
  });

  describe('PUT /settings validation logic', () => {
    it('validates null body', () => {
      const updates = null;
      const isValid = !!(updates && typeof updates === 'object' && !Array.isArray(updates));
      expect(isValid).toBe(false);
    });

    it('validates non-object body', () => {
      const updates = 'string';
      const isValid = !!(updates && typeof updates === 'object' && !Array.isArray(updates));
      expect(isValid).toBe(false);
    });

    it('validates array body', () => {
      const updates = [1, 2, 3];
      const isValid = !!(updates && typeof updates === 'object' && !Array.isArray(updates));
      expect(isValid).toBe(false);
    });

    it('validates valid object', () => {
      const updates = { port: 4000 };
      const isValid = !!(updates && typeof updates === 'object' && !Array.isArray(updates));
      expect(isValid).toBe(true);
    });

    it('validates integer port', () => {
      const port = 4000;
      const isInteger = typeof port === 'number' && Number.isInteger(port);
      expect(isInteger).toBe(true);
    });

    it('rejects non-integer port', () => {
      const port = 3.14;
      const isInteger = typeof port === 'number' && Number.isInteger(port);
      expect(isInteger).toBe(false);
    });

    it('validates port range (valid)', () => {
      const port = 3000;
      const inRange = port >= 1 && port <= 65535;
      expect(inRange).toBe(true);
    });

    it('validates port range (below)', () => {
      const port = 0;
      const inRange = port >= 1 && port <= 65535;
      expect(inRange).toBe(false);
    });

    it('validates port range (above)', () => {
      const port = 65536;
      const inRange = port >= 1 && port <= 65535;
      expect(inRange).toBe(false);
    });

    it('validates boolean auto_start', () => {
      const auto_start = true;
      const isBoolean = typeof auto_start === 'boolean';
      expect(isBoolean).toBe(true);
    });

    it('rejects non-boolean auto_start', () => {
      const auto_start = 'yes';
      const isBoolean = typeof auto_start === 'boolean';
      expect(isBoolean).toBe(false);
    });

    it('validates boolean ghost_mode', () => {
      const ghost_mode = false;
      const isBoolean = typeof ghost_mode === 'boolean';
      expect(isBoolean).toBe(true);
    });

    it('rejects non-boolean ghost_mode', () => {
      const ghost_mode = 1;
      const isBoolean = typeof ghost_mode === 'boolean';
      expect(isBoolean).toBe(false);
    });
  });

  describe('PUT /settings whitelist merge pattern', () => {
    it('merges known fields only (ignores unknown)', () => {
      const config = loadOrCreateUnifiedConfig();
      const updates = {
        port: 5000,
        malicious_key: 'should be ignored',
        another_unknown: true,
      };

      // Simulate the whitelist merge from the route
      const cursorConfig: CursorConfig = {
        enabled: updates.enabled ?? config.cursor?.enabled ?? false,
        port: updates.port ?? config.cursor?.port ?? 3000,
        auto_start: updates.auto_start ?? config.cursor?.auto_start ?? false,
        ghost_mode: updates.ghost_mode ?? config.cursor?.ghost_mode ?? false,
        model: updates.model ?? config.cursor?.model ?? 'gpt-5.3-codex',
        opus_model:
          updates.opus_model !== undefined ? updates.opus_model : config.cursor?.opus_model,
        sonnet_model:
          updates.sonnet_model !== undefined ? updates.sonnet_model : config.cursor?.sonnet_model,
        haiku_model:
          updates.haiku_model !== undefined ? updates.haiku_model : config.cursor?.haiku_model,
      };

      expect(cursorConfig.port).toBe(5000);
      expect(cursorConfig).not.toHaveProperty('malicious_key');
      expect(cursorConfig).not.toHaveProperty('another_unknown');
    });

    it('updates port only', () => {
      const config = loadOrCreateUnifiedConfig();
      config.cursor = {
        enabled: false,
        port: 3000,
        auto_start: false,
        ghost_mode: false,
        model: 'gpt-5.3-codex',
      };
      saveUnifiedConfig(config);

      const updates = { port: 4000 };
      const cursorConfig: CursorConfig = {
        enabled: updates.enabled ?? config.cursor?.enabled ?? false,
        port: updates.port ?? config.cursor?.port ?? 3000,
        auto_start: updates.auto_start ?? config.cursor?.auto_start ?? false,
        ghost_mode: updates.ghost_mode ?? config.cursor?.ghost_mode ?? false,
        model: updates.model ?? config.cursor?.model ?? 'gpt-5.3-codex',
        opus_model:
          updates.opus_model !== undefined ? updates.opus_model : config.cursor?.opus_model,
        sonnet_model:
          updates.sonnet_model !== undefined ? updates.sonnet_model : config.cursor?.sonnet_model,
        haiku_model:
          updates.haiku_model !== undefined ? updates.haiku_model : config.cursor?.haiku_model,
      };

      expect(cursorConfig.port).toBe(4000);
      expect(cursorConfig.auto_start).toBe(false);
      expect(cursorConfig.ghost_mode).toBe(false);
    });

    it('updates auto_start only', () => {
      const config = loadOrCreateUnifiedConfig();
      config.cursor = {
        enabled: false,
        port: 3000,
        auto_start: false,
        ghost_mode: false,
        model: 'gpt-5.3-codex',
      };
      saveUnifiedConfig(config);

      const updates = { auto_start: true };
      const cursorConfig: CursorConfig = {
        enabled: updates.enabled ?? config.cursor?.enabled ?? false,
        port: updates.port ?? config.cursor?.port ?? 3000,
        auto_start: updates.auto_start ?? config.cursor?.auto_start ?? false,
        ghost_mode: updates.ghost_mode ?? config.cursor?.ghost_mode ?? false,
        model: updates.model ?? config.cursor?.model ?? 'gpt-5.3-codex',
        opus_model:
          updates.opus_model !== undefined ? updates.opus_model : config.cursor?.opus_model,
        sonnet_model:
          updates.sonnet_model !== undefined ? updates.sonnet_model : config.cursor?.sonnet_model,
        haiku_model:
          updates.haiku_model !== undefined ? updates.haiku_model : config.cursor?.haiku_model,
      };

      expect(cursorConfig.port).toBe(3000);
      expect(cursorConfig.auto_start).toBe(true);
      expect(cursorConfig.ghost_mode).toBe(false);
    });

    it('updates ghost_mode only', () => {
      const config = loadOrCreateUnifiedConfig();
      config.cursor = {
        enabled: false,
        port: 3000,
        auto_start: false,
        ghost_mode: false,
        model: 'gpt-5.3-codex',
      };
      saveUnifiedConfig(config);

      const updates = { ghost_mode: true };
      const cursorConfig: CursorConfig = {
        enabled: updates.enabled ?? config.cursor?.enabled ?? false,
        port: updates.port ?? config.cursor?.port ?? 3000,
        auto_start: updates.auto_start ?? config.cursor?.auto_start ?? false,
        ghost_mode: updates.ghost_mode ?? config.cursor?.ghost_mode ?? false,
        model: updates.model ?? config.cursor?.model ?? 'gpt-5.3-codex',
        opus_model:
          updates.opus_model !== undefined ? updates.opus_model : config.cursor?.opus_model,
        sonnet_model:
          updates.sonnet_model !== undefined ? updates.sonnet_model : config.cursor?.sonnet_model,
        haiku_model:
          updates.haiku_model !== undefined ? updates.haiku_model : config.cursor?.haiku_model,
      };

      expect(cursorConfig.port).toBe(3000);
      expect(cursorConfig.auto_start).toBe(false);
      expect(cursorConfig.ghost_mode).toBe(true);
    });
  });

  describe('GET /settings/raw logic', () => {
    it('returns defaults when file does not exist', () => {
      const settingsPath = path.join(getCcsDir(), 'cursor.settings.json');
      const exists = fs.existsSync(settingsPath);

      expect(exists).toBe(false);

      const config = loadOrCreateUnifiedConfig();
      const cursorPort = config.cursor?.port ?? 3000;
      const defaultSettings = {
        env: {
          ANTHROPIC_BASE_URL: `http://127.0.0.1:${cursorPort}`,
          ANTHROPIC_AUTH_TOKEN: 'cursor-managed',
          ANTHROPIC_MODEL: 'gpt-5.3-codex',
          ANTHROPIC_DEFAULT_OPUS_MODEL: 'gpt-5.3-codex',
          ANTHROPIC_DEFAULT_SONNET_MODEL: 'gpt-5.3-codex',
          ANTHROPIC_DEFAULT_HAIKU_MODEL: 'gpt-5.3-codex',
        },
      };

      expect(defaultSettings.env.ANTHROPIC_BASE_URL).toContain('http://127.0.0.1:');
      expect(defaultSettings.env.ANTHROPIC_AUTH_TOKEN).toBe('cursor-managed');
      expect(defaultSettings.env.ANTHROPIC_MODEL).toBe('gpt-5.3-codex');
    });

    it('reads existing file', () => {
      const settingsPath = path.join(getCcsDir(), 'cursor.settings.json');
      const testSettings = {
        env: {
          ANTHROPIC_BASE_URL: 'http://127.0.0.1:4000',
          ANTHROPIC_AUTH_TOKEN: 'test-token',
        },
      };

      fs.writeFileSync(settingsPath, JSON.stringify(testSettings, null, 2));
      const exists = fs.existsSync(settingsPath);

      expect(exists).toBe(true);

      const content = fs.readFileSync(settingsPath, 'utf-8');
      const parsed = JSON.parse(content);

      expect(parsed).toEqual(testSettings);
    });
  });

  describe('PUT /settings/raw validation logic', () => {
    it('validates missing settings field', () => {
      const body: { expectedMtime: number; settings?: unknown } = { expectedMtime: Date.now() };
      const isValid = !!(body.settings && typeof body.settings === 'object');
      expect(isValid).toBe(false);
    });

    it('validates non-object settings', () => {
      const body = { settings: 'not an object' };
      const isValid = !!(body.settings && typeof body.settings === 'object');
      expect(isValid).toBe(false);
    });

    it('validates valid settings', () => {
      const body = { settings: { env: { test: 'value' } } };
      const isValid = !!(body.settings && typeof body.settings === 'object');
      expect(isValid).toBe(true);
    });

    it('writes settings file atomically', () => {
      const settingsPath = path.join(getCcsDir(), 'cursor.settings.json');
      const testSettings = {
        env: {
          ANTHROPIC_BASE_URL: 'http://127.0.0.1:5000',
          ANTHROPIC_AUTH_TOKEN: 'new-token',
        },
      };

      // Simulate atomic write
      const tempPath = settingsPath + '.tmp';
      fs.writeFileSync(tempPath, JSON.stringify(testSettings, null, 2) + '\n');
      fs.renameSync(tempPath, settingsPath);

      const exists = fs.existsSync(settingsPath);
      expect(exists).toBe(true);

      const written = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
      expect(written).toEqual(testSettings);
    });

    it('detects mtime conflict', () => {
      const settingsPath = path.join(getCcsDir(), 'cursor.settings.json');
      const initialSettings = { env: { test: 'initial' } };

      fs.writeFileSync(settingsPath, JSON.stringify(initialSettings));
      const stat = fs.statSync(settingsPath);

      const expectedMtime = stat.mtimeMs - 5000; // 5 seconds in the past
      const hasConflict = Math.abs(stat.mtimeMs - expectedMtime) > 1000;

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

    it('requires expectedMtime when file already exists', () => {
      const settingsPath = path.join(getCcsDir(), 'cursor.settings.json');
      fs.writeFileSync(settingsPath, JSON.stringify({ env: { test: 'existing' } }));

      const expectedMtime = undefined;
      const shouldRequireMtime =
        fs.existsSync(settingsPath) &&
        (typeof expectedMtime !== 'number' || !Number.isFinite(expectedMtime));

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

    it('does not require expectedMtime when file does not exist', () => {
      const settingsPath = path.join(getCcsDir(), 'cursor.settings.json');
      if (fs.existsSync(settingsPath)) {
        fs.rmSync(settingsPath, { force: true });
      }

      const expectedMtime = undefined;
      const shouldRequireMtime =
        fs.existsSync(settingsPath) &&
        (typeof expectedMtime !== 'number' || !Number.isFinite(expectedMtime));

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

    it('allows write when mtime matches', () => {
      const settingsPath = path.join(getCcsDir(), 'cursor.settings.json');
      const initialSettings = { env: { test: 'initial' } };

      fs.writeFileSync(settingsPath, JSON.stringify(initialSettings));
      const stat = fs.statSync(settingsPath);

      const expectedMtime = stat.mtimeMs;
      const hasConflict = Math.abs(stat.mtimeMs - expectedMtime) > 1000;

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