import { describe, it, expect, beforeEach, vi } from 'vitest';
import Fastify, { FastifyInstance } from 'fastify';
import characterRoutes from './characters.js';
import type { CharacterDNA } from '../types/character.js';

function createMockCharacterEngine() {
  return {
    listCharacters: vi.fn(),
    getCharacter: vi.fn(),
    createCharacter: vi.fn(),
    updateCharacter: vi.fn(),
    deleteCharacter: vi.fn(),
    cloneCharacter: vi.fn(),
    activateCharacter: vi.fn(),
    deactivateCharacter: vi.fn(),
  };
}

function createMockDataStore() {
  return {
    getCharacter: vi.fn(),
    saveCharacter: vi.fn(),
    getConversationProfile: vi.fn(),
    saveConversationProfile: vi.fn(),
    getReferenceImagesDir: vi.fn(),
  };
}

const sampleCharacter: CharacterDNA = {
  id: 'char-1',
  createdAt: '2024-01-01T00:00:00.000Z',
  updatedAt: '2024-01-01T00:00:00.000Z',
  status: 'active',
  identity: { name: 'Test', age: 25, nationality: 'TR', city: 'Istanbul', occupation: 'Model', personality_tags: [], mbti: 'ENFP', backstory: '' },
  physicalMeasurements: { height_cm: 170, weight_kg: 55, body_type: 'slim', measurements: { bust: 85, waist: 60, hips: 90 } },
  skinMap: { base: '#F5D0A9', shadow: '#D4A574', highlight: '#FFE4C4' },
  faceAnatomy: { eye_shape: 'almond', eye_color: 'brown', eyebrow_shape: 'arched', nose_type: 'straight', lip_shape: 'full', cheekbones: 'high', jawline: 'soft', distinctive_marks: [] },
  hairDNA: { style: 'wavy', length: 'long', texture: 'smooth', root_color: '#3B2F2F', mid_color: '#5C4033', highlight_color: '#8B7355' },
  bodyBiometrics: { body_type_detail: 'ectomorph', posture: 'upright', skin_texture: 'smooth' },
  profileView: { best_angle: 'three-quarter', signature_pose: 'hand-on-hip' },
  expressionMotion: { default_expression: 'smile', smile_type: 'closed', gaze_style: 'direct' },
  makeupStyle: { philosophy: 'natural', skin_finish: 'dewy', colors: { primary: '#C48A6A', secondary: '#B5838D', accent: '#E8A87C' } },
  fashionStyle: { aesthetic: 'casual-chic', main_colors: ['#FFFFFF', '#000000'], signature_pieces: ['blazer'] },
  technicalSettings: { camera_body: 'Canon R5', portrait_lens: '85mm f/1.4', full_body_lens: '35mm f/1.4', film_stock: 'Portra 400' },
};

describe('characters API credentials endpoints', () => {
  let app: FastifyInstance;
  let mockEngine: ReturnType<typeof createMockCharacterEngine>;
  let mockStore: ReturnType<typeof createMockDataStore>;

  beforeEach(async () => {
    vi.clearAllMocks();
    mockEngine = createMockCharacterEngine();
    mockStore = createMockDataStore();

    app = Fastify();
    await app.register(characterRoutes, {
      characterEngine: mockEngine as any,
      dataStore: mockStore as any,
    });
    await app.ready();
  });

  describe('GET /characters/:id/api-credentials', () => {
    it('should return api credentials when they exist', async () => {
      const charWithCreds = { ...sampleCharacter, apiCredentials: { telegramApiId: 12345, telegramApiHash: 'abc123hash' } };
      mockStore.getCharacter.mockResolvedValue(charWithCreds);

      const res = await app.inject({ method: 'GET', url: '/characters/char-1/api-credentials' });

      expect(res.statusCode).toBe(200);
      const body = res.json();
      expect(body.success).toBe(true);
      expect(body.data).toEqual({ telegramApiId: 12345, telegramApiHash: 'abc123hash' });
    });

    it('should return null data when credentials are not set', async () => {
      mockStore.getCharacter.mockResolvedValue(sampleCharacter);

      const res = await app.inject({ method: 'GET', url: '/characters/char-1/api-credentials' });

      expect(res.statusCode).toBe(200);
      const body = res.json();
      expect(body.success).toBe(true);
      expect(body.data).toBeNull();
    });

    it('should return 404 when character not found', async () => {
      mockStore.getCharacter.mockResolvedValue(null);

      const res = await app.inject({ method: 'GET', url: '/characters/nonexistent/api-credentials' });

      expect(res.statusCode).toBe(404);
      expect(res.json().success).toBe(false);
      expect(res.json().error).toBe('Character not found');
    });
  });

  describe('PUT /characters/:id/api-credentials', () => {
    it('should update api credentials successfully', async () => {
      mockStore.getCharacter.mockResolvedValue({ ...sampleCharacter });
      mockStore.saveCharacter.mockResolvedValue(undefined);

      const res = await app.inject({
        method: 'PUT',
        url: '/characters/char-1/api-credentials',
        payload: { telegramApiId: 99999, telegramApiHash: 'newhash456' },
      });

      expect(res.statusCode).toBe(200);
      const body = res.json();
      expect(body.success).toBe(true);
      expect(body.data).toEqual({ telegramApiId: 99999, telegramApiHash: 'newhash456' });
      expect(mockStore.saveCharacter).toHaveBeenCalledTimes(1);

      const savedChar = mockStore.saveCharacter.mock.calls[0][0];
      expect(savedChar.apiCredentials).toEqual({ telegramApiId: 99999, telegramApiHash: 'newhash456' });
    });

    it('should return 404 when character not found', async () => {
      mockStore.getCharacter.mockResolvedValue(null);

      const res = await app.inject({
        method: 'PUT',
        url: '/characters/nonexistent/api-credentials',
        payload: { telegramApiId: 12345, telegramApiHash: 'hash123' },
      });

      expect(res.statusCode).toBe(404);
      expect(res.json().success).toBe(false);
    });

    it('should return 400 when body is missing', async () => {
      const res = await app.inject({
        method: 'PUT',
        url: '/characters/char-1/api-credentials',
      });

      expect(res.statusCode).toBe(400);
      expect(res.json().success).toBe(false);
    });

    it('should return 400 when telegramApiId is missing', async () => {
      const res = await app.inject({
        method: 'PUT',
        url: '/characters/char-1/api-credentials',
        payload: { telegramApiHash: 'hash123' },
      });

      expect(res.statusCode).toBe(400);
      expect(res.json().success).toBe(false);
    });

    it('should return 400 when telegramApiHash is missing', async () => {
      const res = await app.inject({
        method: 'PUT',
        url: '/characters/char-1/api-credentials',
        payload: { telegramApiId: 12345 },
      });

      expect(res.statusCode).toBe(400);
      expect(res.json().success).toBe(false);
    });

    it('should return 400 when telegramApiId is not a number', async () => {
      const res = await app.inject({
        method: 'PUT',
        url: '/characters/char-1/api-credentials',
        payload: { telegramApiId: 'not-a-number', telegramApiHash: 'hash123' },
      });

      expect(res.statusCode).toBe(400);
      expect(res.json().success).toBe(false);
    });

    it('should return 400 when telegramApiHash is empty string', async () => {
      const res = await app.inject({
        method: 'PUT',
        url: '/characters/char-1/api-credentials',
        payload: { telegramApiId: 12345, telegramApiHash: '   ' },
      });

      expect(res.statusCode).toBe(400);
      expect(res.json().success).toBe(false);
    });

    it('should trim telegramApiHash whitespace', async () => {
      mockStore.getCharacter.mockResolvedValue({ ...sampleCharacter });
      mockStore.saveCharacter.mockResolvedValue(undefined);

      const res = await app.inject({
        method: 'PUT',
        url: '/characters/char-1/api-credentials',
        payload: { telegramApiId: 12345, telegramApiHash: '  hash123  ' },
      });

      expect(res.statusCode).toBe(200);
      expect(res.json().data.telegramApiHash).toBe('hash123');
    });
  });
});
