import { describe, it, expect, vi, beforeEach } from 'vitest';
import { VLMAnalyzer } from './vlm.js';
import type { OpenRouterClient, ChatMessage } from './client.js';
import type { CharacterDNA } from '../../types/character.js';
import type { OpenRouterResponse } from '../../types/api.js';

// Mock logger
vi.mock('../../utils/logger.js', () => ({
  createModuleLogger: () => ({
    debug: vi.fn(),
    info: vi.fn(),
    warn: vi.fn(),
    error: vi.fn(),
  }),
}));

// Mock config (needed by createVLMAnalyzer → createOpenRouterClient)
vi.mock('../../utils/config.js', () => ({
  config: { openrouterApiKey: 'test-key' },
}));

const SAMPLE_DNA: CharacterDNA = {
  id: 'char-001',
  createdAt: '2024-01-01T00:00:00Z',
  updatedAt: '2024-01-01T00:00:00Z',
  status: 'active',
  identity: {
    name: 'Aylin',
    age: 24,
    nationality: 'Turkish',
    city: 'Istanbul',
    occupation: 'Model',
    personality_tags: ['friendly'],
    mbti: 'ENFP',
    backstory: 'A young model from Istanbul.',
  },
  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: '#8B4513',
    eyebrow_shape: 'arched',
    nose_type: 'straight',
    lip_shape: 'full',
    cheekbones: 'high',
    jawline: 'soft',
    distinctive_marks: [],
  },
  hairDNA: {
    style: 'wavy',
    length: 'long',
    texture: 'silky',
    root_color: '#3B2F2F',
    mid_color: '#5C4033',
    highlight_color: '#8B6914',
  },
  bodyBiometrics: { body_type_detail: 'ectomorph', posture: 'upright', skin_texture: 'smooth' },
  profileView: { best_angle: 'left-45', signature_pose: 'hand-on-hip' },
  expressionMotion: { default_expression: 'soft smile', smile_type: 'closed-lip', gaze_style: 'direct' },
  makeupStyle: {
    philosophy: 'natural',
    skin_finish: 'dewy',
    colors: { primary: '#C48A6A', secondary: '#B5651D', accent: '#FF6B6B' },
  },
  fashionStyle: { aesthetic: 'casual chic', main_colors: ['#FFFFFF', '#000000'], signature_pieces: ['blazer'] },
  technicalSettings: { camera_body: 'Sony A7IV', portrait_lens: '85mm f/1.4', full_body_lens: '35mm f/1.8', film_stock: 'Portra 400' },
};

function makeMockClient(responseContent: string): OpenRouterClient {
  const mockResponse: OpenRouterResponse = {
    id: 'resp-1',
    choices: [{ message: { role: 'assistant', content: responseContent }, finish_reason: 'stop' }],
    usage: { prompt_tokens: 100, completion_tokens: 50, total_tokens: 150 },
  };
  return {
    chatCompletionWithVision: vi.fn().mockResolvedValue(mockResponse),
    chatCompletion: vi.fn(),
  } as unknown as OpenRouterClient;
}

describe('VLMAnalyzer', () => {
  let analyzer: VLMAnalyzer;
  let mockClient: OpenRouterClient;

  beforeEach(() => {
    mockClient = makeMockClient(
      '{"characterAccuracy":8.5,"bodyConsistency":7.0,"environmentQuality":8.0,"overallAesthetic":9.0,"feedback":"Good match"}',
    );
    analyzer = new VLMAnalyzer(mockClient);
  });

  describe('buildScoringPrompt', () => {
    it('includes character identity details', () => {
      const prompt = analyzer.buildScoringPrompt(SAMPLE_DNA);
      expect(prompt).toContain('Aylin');
      expect(prompt).toContain('24');
    });

    it('includes physical measurements', () => {
      const prompt = analyzer.buildScoringPrompt(SAMPLE_DNA);
      expect(prompt).toContain('slim');
      expect(prompt).toContain('170cm');
    });

    it('includes skin HEX codes', () => {
      const prompt = analyzer.buildScoringPrompt(SAMPLE_DNA);
      expect(prompt).toContain('#F5D0A9');
      expect(prompt).toContain('#D4A574');
    });

    it('includes hair details', () => {
      const prompt = analyzer.buildScoringPrompt(SAMPLE_DNA);
      expect(prompt).toContain('wavy');
      expect(prompt).toContain('#3B2F2F');
    });

    it('includes scoring categories', () => {
      const prompt = analyzer.buildScoringPrompt(SAMPLE_DNA);
      expect(prompt).toContain('characterAccuracy');
      expect(prompt).toContain('bodyConsistency');
      expect(prompt).toContain('environmentQuality');
      expect(prompt).toContain('overallAesthetic');
    });

    it('requests JSON response format', () => {
      const prompt = analyzer.buildScoringPrompt(SAMPLE_DNA);
      expect(prompt).toContain('JSON');
    });
  });

  describe('parseScores', () => {
    it('parses valid JSON response', () => {
      const scores = analyzer.parseScores(
        '{"characterAccuracy":8,"bodyConsistency":7,"environmentQuality":9,"overallAesthetic":8,"feedback":"ok"}',
      );
      expect(scores).toEqual({
        characterAccuracy: 8,
        bodyConsistency: 7,
        environmentQuality: 9,
        overallAesthetic: 8,
      });
    });

    it('extracts JSON from markdown code block', () => {
      const scores = analyzer.parseScores(
        '```json\n{"characterAccuracy":6,"bodyConsistency":5,"environmentQuality":7,"overallAesthetic":8}\n```',
      );
      expect(scores.characterAccuracy).toBe(6);
    });

    it('clamps scores to 0-10 range', () => {
      const scores = analyzer.parseScores(
        '{"characterAccuracy":15,"bodyConsistency":-3,"environmentQuality":10,"overallAesthetic":0}',
      );
      expect(scores.characterAccuracy).toBe(10);
      expect(scores.bodyConsistency).toBe(0);
      expect(scores.environmentQuality).toBe(10);
      expect(scores.overallAesthetic).toBe(0);
    });

    it('treats non-numeric values as 0', () => {
      const scores = analyzer.parseScores(
        '{"characterAccuracy":"bad","bodyConsistency":null,"environmentQuality":7,"overallAesthetic":8}',
      );
      expect(scores.characterAccuracy).toBe(0);
      expect(scores.bodyConsistency).toBe(0);
    });

    it('throws when no JSON found', () => {
      expect(() => analyzer.parseScores('No scores here')).toThrow('No JSON object found');
    });
  });

  describe('analyzeImage', () => {
    it('returns a QualityReport with correct weighted total', async () => {
      const report = await analyzer.analyzeImage(
        'https://example.com/img.jpg',
        SAMPLE_DNA,
        'img-001',
        'task-001',
      );

      // 8.5*0.4 + 7.0*0.2 + 8.0*0.2 + 9.0*0.2 = 3.4 + 1.4 + 1.6 + 1.8 = 8.2
      expect(report.totalScore).toBe(8.2);
      expect(report.passed).toBe(true);
      expect(report.imageId).toBe('img-001');
      expect(report.characterId).toBe('char-001');
      expect(report.taskId).toBe('task-001');
      expect(report.feedback).toBe('Good match');
      expect(report.scores.characterAccuracy).toBe(8.5);
    });

    it('sends image_url content to the client', async () => {
      await analyzer.analyzeImage('https://example.com/img.jpg', SAMPLE_DNA, 'img-001', 'task-001');

      const call = (mockClient.chatCompletionWithVision as ReturnType<typeof vi.fn>).mock.calls[0];
      const messages = call[0] as ChatMessage[];

      expect(messages).toHaveLength(2);
      expect(messages[0].role).toBe('system');
      expect(messages[1].role).toBe('user');

      const userContent = messages[1].content as Array<{ type: string; image_url?: { url: string } }>;
      expect(userContent).toHaveLength(2);
      expect(userContent[1].type).toBe('image_url');
      expect(userContent[1].image_url?.url).toBe('https://example.com/img.jpg');
    });

    it('marks report as failed when below threshold', async () => {
      const lowClient = makeMockClient(
        '{"characterAccuracy":3,"bodyConsistency":4,"environmentQuality":5,"overallAesthetic":4,"feedback":"Poor"}',
      );
      const lowAnalyzer = new VLMAnalyzer(lowClient);

      const report = await lowAnalyzer.analyzeImage(
        'https://example.com/img.jpg',
        SAMPLE_DNA,
        'img-002',
        'task-002',
      );

      // 3*0.4 + 4*0.2 + 5*0.2 + 4*0.2 = 1.2 + 0.8 + 1.0 + 0.8 = 3.8
      expect(report.totalScore).toBe(3.8);
      expect(report.passed).toBe(false);
    });

    it('uses custom threshold when provided', async () => {
      const report = await analyzer.analyzeImage(
        'https://example.com/img.jpg',
        SAMPLE_DNA,
        'img-003',
        'task-003',
        9.0,
      );

      // totalScore = 8.2, threshold = 9.0
      expect(report.passed).toBe(false);
    });

    it('includes createdAt timestamp', async () => {
      const report = await analyzer.analyzeImage(
        'https://example.com/img.jpg',
        SAMPLE_DNA,
        'img-004',
        'task-004',
      );

      expect(report.createdAt).toBeTruthy();
      expect(() => new Date(report.createdAt)).not.toThrow();
    });
  });
});

describe('createVLMAnalyzer', () => {
  it('creates analyzer from factory function', async () => {
    const { createVLMAnalyzer } = await import('./vlm.js');
    const analyzer = createVLMAnalyzer();
    expect(analyzer).toBeInstanceOf(VLMAnalyzer);
  });
});
