import { describe, it, expect } from 'vitest'
import { validateResponseSchema } from '../response-schema-validator'
import { LLMResponseParseError } from '../errors'

describe('validateResponseSchema', () => {
  const classificationSchema = {
    type: 'object',
    properties: {
      category: { type: 'string', enum: ['interested', 'not-interested', 'bounced'] },
      confidence: { type: 'number' },
      reasoning: { type: 'string' }
    },
    required: ['category', 'confidence', 'reasoning']
  }

  it('passes for valid output matching schema', () => {
    const output = { category: 'interested', confidence: 0.95, reasoning: 'Clear interest shown' }
    expect(() => validateResponseSchema(output, classificationSchema)).not.toThrow()
  })

  it('throws LLMResponseParseError for null output', () => {
    expect(() => validateResponseSchema(null, classificationSchema)).toThrow(LLMResponseParseError)
    expect(() => validateResponseSchema(null, classificationSchema)).toThrow('root is null')
  })

  it('throws LLMResponseParseError for undefined output', () => {
    expect(() => validateResponseSchema(undefined, classificationSchema)).toThrow(LLMResponseParseError)
    expect(() => validateResponseSchema(undefined, classificationSchema)).toThrow('root is undefined')
  })

  it('throws for missing required fields with descriptive message', () => {
    const output = { confidence: 0.9 }
    try {
      validateResponseSchema(output, classificationSchema)
      expect.unreachable('Should have thrown')
    } catch (error) {
      expect(error).toBeInstanceOf(LLMResponseParseError)
      const msg = (error as LLMResponseParseError).message
      expect(msg).toContain("missing required field 'category'")
      expect(msg).toContain("missing required field 'reasoning'")
      expect(msg).not.toContain('confidence') // confidence is present
    }
  })

  it('throws for wrong type', () => {
    const output = { category: 123, confidence: 0.9, reasoning: 'test' }
    expect(() => validateResponseSchema(output, classificationSchema)).toThrow(
      "category: expected type 'string', got 'number'"
    )
  })

  it('throws for invalid enum value', () => {
    const output = { category: 'maybe', confidence: 0.5, reasoning: 'unclear' }
    expect(() => validateResponseSchema(output, classificationSchema)).toThrow('not in enum')
  })

  it('is retryable', () => {
    try {
      validateResponseSchema(null, classificationSchema)
    } catch (error) {
      expect((error as LLMResponseParseError).isRetryable()).toBe(true)
    }
  })

  it('skips validation when schema is null/undefined', () => {
    expect(() => validateResponseSchema({ anything: true }, null)).not.toThrow()
    expect(() => validateResponseSchema({ anything: true }, undefined)).not.toThrow()
  })

  it('validates nested objects', () => {
    const schema = {
      type: 'object',
      properties: {
        result: {
          type: 'object',
          properties: {
            score: { type: 'number' }
          },
          required: ['score']
        }
      },
      required: ['result']
    }

    expect(() => validateResponseSchema({ result: { score: 10 } }, schema)).not.toThrow()
    expect(() => validateResponseSchema({ result: {} }, schema)).toThrow("missing required field 'result.score'")
  })

  it('validates arrays with items schema', () => {
    const schema = {
      type: 'object',
      properties: {
        tags: {
          type: 'array',
          items: { type: 'string' }
        }
      },
      required: ['tags']
    }

    expect(() => validateResponseSchema({ tags: ['a', 'b'] }, schema)).not.toThrow()
    expect(() => validateResponseSchema({ tags: ['a', 123] }, schema)).toThrow(
      "tags[1]: expected type 'string', got 'number'"
    )
  })

  it('includes schema required fields in error context', () => {
    try {
      validateResponseSchema({}, classificationSchema)
    } catch (error) {
      const ctx = (error as LLMResponseParseError).context
      expect(ctx?.schemaRequired).toEqual(['category', 'confidence', 'reasoning'])
    }
  })
})
