import { describe, it, expect } from 'vitest';
import {
  validateDNA,
  validateHexColor,
  validateMBTI,
  fillDefaults,
  VALID_MBTI_TYPES,
} from './dna-schema.js';
import type { CharacterDNA } from '../../types/character.js';

// Helper: build a valid DNA object for testing
function validDNA(): CharacterDNA {
  return fillDefaults({
    id: 'test-id-123',
    createdAt: '2024-01-01T00:00:00.000Z',
    updatedAt: '2024-01-01T00:00:00.000Z',
    status: 'active',
    identity: {
      name: 'Ayşe',
      age: 24,
      nationality: 'Turkish',
      city: 'Istanbul',
      occupation: 'Model',
      personality_tags: ['friendly', 'outgoing'],
      mbti: 'ENFP',
      backstory: 'A young model from Istanbul.',
    },
  });
}

describe('validateHexColor', () => {
  it('accepts valid 6-digit hex colors', () => {
    expect(validateHexColor('#FF0000')).toBe(true);
    expect(validateHexColor('#00ff00')).toBe(true);
    expect(validateHexColor('#1A2B3C')).toBe(true);
    expect(validateHexColor('#000000')).toBe(true);
    expect(validateHexColor('#FFFFFF')).toBe(true);
  });

  it('rejects invalid hex colors', () => {
    expect(validateHexColor('')).toBe(false);
    expect(validateHexColor('FF0000')).toBe(false);     // missing #
    expect(validateHexColor('#FFF')).toBe(false);        // 3-digit
    expect(validateHexColor('#GGGGGG')).toBe(false);     // invalid chars
    expect(validateHexColor('#FF00001')).toBe(false);    // 7 digits
    expect(validateHexColor('#FF000')).toBe(false);      // 5 digits
  });
});

describe('validateMBTI', () => {
  it('has exactly 16 valid types', () => {
    expect(VALID_MBTI_TYPES).toHaveLength(16);
  });

  it('accepts all 16 valid MBTI types', () => {
    for (const t of VALID_MBTI_TYPES) {
      expect(validateMBTI(t)).toBe(true);
    }
  });

  it('is case-insensitive', () => {
    expect(validateMBTI('enfp')).toBe(true);
    expect(validateMBTI('Intj')).toBe(true);
  });

  it('rejects invalid MBTI types', () => {
    expect(validateMBTI('XXXX')).toBe(false);
    expect(validateMBTI('')).toBe(false);
    expect(validateMBTI('ENF')).toBe(false);
  });
});


describe('validateDNA', () => {
  it('returns valid for a complete DNA object', () => {
    const result = validateDNA(validDNA());
    expect(result.valid).toBe(true);
    expect(result.errors).toHaveLength(0);
  });

  it('rejects null/undefined/non-object', () => {
    expect(validateDNA(null).valid).toBe(false);
    expect(validateDNA(undefined).valid).toBe(false);
    expect(validateDNA('string').valid).toBe(false);
    expect(validateDNA(42).valid).toBe(false);
  });

  it('reports missing top-level fields', () => {
    const result = validateDNA({});
    expect(result.valid).toBe(false);
    expect(result.errors.some(e => e.includes('id'))).toBe(true);
    expect(result.errors.some(e => e.includes('status'))).toBe(true);
    expect(result.errors.some(e => e.includes('identity'))).toBe(true);
  });

  it('validates identity.mbti', () => {
    const dna = validDNA();
    (dna.identity as Record<string, unknown>).mbti = 'INVALID';
    const result = validateDNA(dna);
    expect(result.valid).toBe(false);
    expect(result.errors.some(e => e.includes('mbti'))).toBe(true);
  });

  it('validates height_cm range', () => {
    const dna = validDNA();
    dna.physicalMeasurements.height_cm = 10; // below 50
    const result = validateDNA(dna);
    expect(result.valid).toBe(false);
    expect(result.errors.some(e => e.includes('height_cm'))).toBe(true);
  });

  it('validates weight_kg range', () => {
    const dna = validDNA();
    dna.physicalMeasurements.weight_kg = 500; // above 300
    const result = validateDNA(dna);
    expect(result.valid).toBe(false);
    expect(result.errors.some(e => e.includes('weight_kg'))).toBe(true);
  });

  it('validates measurement ranges', () => {
    const dna = validDNA();
    dna.physicalMeasurements.measurements.bust = 10; // below 50
    const result = validateDNA(dna);
    expect(result.valid).toBe(false);
    expect(result.errors.some(e => e.includes('bust'))).toBe(true);
  });

  it('validates HEX colors in skinMap', () => {
    const dna = validDNA();
    (dna.skinMap as Record<string, unknown>).base = 'not-hex';
    const result = validateDNA(dna);
    expect(result.valid).toBe(false);
    expect(result.errors.some(e => e.includes('skinMap.base'))).toBe(true);
  });

  it('validates HEX colors in hairDNA', () => {
    const dna = validDNA();
    (dna.hairDNA as Record<string, unknown>).root_color = 'bad';
    const result = validateDNA(dna);
    expect(result.valid).toBe(false);
    expect(result.errors.some(e => e.includes('hairDNA.root_color'))).toBe(true);
  });

  it('validates HEX colors in makeupStyle.colors', () => {
    const dna = validDNA();
    (dna.makeupStyle.colors as Record<string, unknown>).primary = 'nope';
    const result = validateDNA(dna);
    expect(result.valid).toBe(false);
    expect(result.errors.some(e => e.includes('makeupStyle.colors.primary'))).toBe(true);
  });

  it('validates HEX colors in fashionStyle.main_colors', () => {
    const dna = validDNA();
    dna.fashionStyle.main_colors = ['#000000', 'invalid'];
    const result = validateDNA(dna);
    expect(result.valid).toBe(false);
    expect(result.errors.some(e => e.includes('fashionStyle.main_colors'))).toBe(true);
  });

  it('validates required string fields in technicalSettings', () => {
    const dna = validDNA();
    (dna.technicalSettings as Record<string, unknown>).camera_body = '';
    const result = validateDNA(dna);
    expect(result.valid).toBe(false);
    expect(result.errors.some(e => e.includes('technicalSettings.camera_body'))).toBe(true);
  });

  it('validates all 11 components are checked', () => {
    // An empty object should report errors for all 11 sections
    const result = validateDNA({
      id: 'x', createdAt: 'x', updatedAt: 'x', status: 'active',
    });
    expect(result.valid).toBe(false);
    const sections = [
      'identity', 'physicalMeasurements', 'skinMap', 'faceAnatomy',
      'hairDNA', 'bodyBiometrics', 'profileView', 'expressionMotion',
      'makeupStyle', 'fashionStyle', 'technicalSettings',
    ];
    for (const s of sections) {
      expect(result.errors.some(e => e.includes(s))).toBe(true);
    }
  });
});

describe('fillDefaults', () => {
  it('returns a complete CharacterDNA from empty partial', () => {
    const dna = fillDefaults({});
    const result = validateDNA(dna);
    expect(result.valid).toBe(true);
    expect(result.errors).toHaveLength(0);
  });

  it('generates an id if not provided', () => {
    const dna = fillDefaults({});
    expect(dna.id).toBeTruthy();
    expect(typeof dna.id).toBe('string');
  });

  it('preserves provided values', () => {
    const dna = fillDefaults({
      identity: {
        name: 'Custom Name',
        age: 30,
        nationality: 'German',
        city: 'Berlin',
        occupation: 'Engineer',
        personality_tags: ['smart'],
        mbti: 'INTJ',
        backstory: 'A story',
      },
    });
    expect(dna.identity.name).toBe('Custom Name');
    expect(dna.identity.age).toBe(30);
    expect(dna.identity.nationality).toBe('German');
  });

  it('fills default status as creating', () => {
    const dna = fillDefaults({});
    expect(dna.status).toBe('creating');
  });

  it('fills default HEX colors that pass validation', () => {
    const dna = fillDefaults({});
    expect(validateHexColor(dna.skinMap.base)).toBe(true);
    expect(validateHexColor(dna.skinMap.shadow)).toBe(true);
    expect(validateHexColor(dna.skinMap.highlight)).toBe(true);
    expect(validateHexColor(dna.hairDNA.root_color)).toBe(true);
    expect(validateHexColor(dna.makeupStyle.colors.primary)).toBe(true);
    for (const c of dna.fashionStyle.main_colors) {
      expect(validateHexColor(c)).toBe(true);
    }
  });

  it('fills default MBTI that passes validation', () => {
    const dna = fillDefaults({});
    expect(validateMBTI(dna.identity.mbti)).toBe(true);
  });

  it('fills default physical measurements within valid ranges', () => {
    const dna = fillDefaults({});
    expect(dna.physicalMeasurements.height_cm).toBeGreaterThanOrEqual(50);
    expect(dna.physicalMeasurements.height_cm).toBeLessThanOrEqual(250);
    expect(dna.physicalMeasurements.weight_kg).toBeGreaterThanOrEqual(20);
    expect(dna.physicalMeasurements.weight_kg).toBeLessThanOrEqual(300);
  });
});
