import { describe, it, expect } from 'vitest'
import { complianceScopeEngine } from '../compliance-scope-engine'

// Helper: create an answer object (engine reads value + questionId)
function ans(questionId: string, value: unknown) {
  return { questionId, value } as any
}

// Helper: create a minimal triggered-trigger (engine shape, not types shape)
function trigger(ruleId: string, minimumLevel: string, opts: Record<string, unknown> = {}) {
  return { ruleId, minimumLevel, mandatoryDocuments: [], requiresDSFA: false, category: 'test', ...opts } as any
}

// ============================================================================
// calculateScores
// ============================================================================

describe('calculateScores', () => {
  it('returns zero composite for empty answers', () => {
    const scores = complianceScopeEngine.calculateScores([])
    expect(scores.composite_score).toBe(0)
    expect(scores.risk_score).toBe(0)
    expect(scores.complexity_score).toBe(0)
    expect(scores.assurance_need).toBe(0)
  })

  it('all-false boolean answers → zero composite', () => {
    const scores = complianceScopeEngine.calculateScores([
      ans('data_art9', false),
      ans('data_minors', false),
      ans('proc_ai_usage', false),
    ])
    expect(scores.composite_score).toBe(0)
  })

  it('boolean true answer increases risk score', () => {
    const scoresFalse = complianceScopeEngine.calculateScores([ans('data_art9', false)])
    const scoresTrue = complianceScopeEngine.calculateScores([ans('data_art9', true)])
    expect(scoresTrue.risk_score).toBeGreaterThan(scoresFalse.risk_score)
  })

  it('composite is weighted sum: risk×0.4 + complexity×0.3 + assurance×0.3', () => {
    const scores = complianceScopeEngine.calculateScores([ans('data_art9', true)])
    const expected = Math.round((scores.risk_score * 0.4 + scores.complexity_score * 0.3 + scores.assurance_need * 0.3) * 10) / 10
    expect(scores.composite_score).toBe(expected)
  })

  it('numeric answer uses logarithmic normalization — higher value → higher score', () => {
    const scoresLow = complianceScopeEngine.calculateScores([ans('data_volume', 10)])
    const scoresHigh = complianceScopeEngine.calculateScores([ans('data_volume', 999)])
    expect(scoresHigh.composite_score).toBeGreaterThan(scoresLow.composite_score)
  })

  it('array answer score proportional to count (max 1.0 at 5+)', () => {
    const scores1 = complianceScopeEngine.calculateScores([ans('data_art9', ['gesundheit'])])
    const scores5 = complianceScopeEngine.calculateScores([
      ans('data_art9', ['gesundheit', 'biometrie', 'genetik', 'politisch', 'religion']),
    ])
    expect(scores5.composite_score).toBeGreaterThan(scores1.composite_score)
  })

  it('empty array answer → zero contribution', () => {
    const scoresEmpty = complianceScopeEngine.calculateScores([ans('data_art9', [])])
    const scoresNone = complianceScopeEngine.calculateScores([])
    expect(scoresEmpty.composite_score).toBe(scoresNone.composite_score)
  })
})

// ============================================================================
// determineLevel
// ============================================================================

describe('determineLevel', () => {
  it('composite ≤25 → L1', () => {
    const level = complianceScopeEngine.determineLevel({ composite_score: 20 } as any, [])
    expect(level).toBe('L1')
  })

  it('composite exactly 25 → L1', () => {
    const level = complianceScopeEngine.determineLevel({ composite_score: 25 } as any, [])
    expect(level).toBe('L1')
  })

  it('composite 26–50 → L2', () => {
    const level = complianceScopeEngine.determineLevel({ composite_score: 40 } as any, [])
    expect(level).toBe('L2')
  })

  it('composite exactly 50 → L2', () => {
    const level = complianceScopeEngine.determineLevel({ composite_score: 50 } as any, [])
    expect(level).toBe('L2')
  })

  it('composite 51–75 → L3', () => {
    const level = complianceScopeEngine.determineLevel({ composite_score: 60 } as any, [])
    expect(level).toBe('L3')
  })

  it('composite >75 → L4', () => {
    const level = complianceScopeEngine.determineLevel({ composite_score: 80 } as any, [])
    expect(level).toBe('L4')
  })

  it('hard trigger with minimumLevel L3 overrides score-based L1', () => {
    const t = trigger('HT-A01', 'L3')
    const level = complianceScopeEngine.determineLevel({ composite_score: 10 } as any, [t])
    expect(level).toBe('L3')
  })

  it('hard trigger with minimumLevel L4 overrides score-based L2', () => {
    const t = trigger('HT-F01', 'L4')
    const level = complianceScopeEngine.determineLevel({ composite_score: 40 } as any, [t])
    expect(level).toBe('L4')
  })

  it('level = max(score-level, trigger-level) — score wins when higher', () => {
    const t = trigger('HT-B01', 'L2')
    // score gives L3, trigger gives L2 → max = L3
    const level = complianceScopeEngine.determineLevel({ composite_score: 60 } as any, [t])
    expect(level).toBe('L3')
  })

  it('no triggers with zero composite → L1', () => {
    const level = complianceScopeEngine.determineLevel({ composite_score: 0 } as any, [])
    expect(level).toBe('L1')
  })
})

// ============================================================================
// evaluateHardTriggers
// ============================================================================

describe('evaluateHardTriggers', () => {
  it('empty answers → no triggers', () => {
    const triggers = complianceScopeEngine.evaluateHardTriggers([])
    expect(triggers).toHaveLength(0)
  })

  it('art9 health data answer triggers category art9 with minimumLevel L3', () => {
    const triggers = complianceScopeEngine.evaluateHardTriggers([
      ans('data_art9', ['gesundheit']),
    ])
    const art9 = triggers.find((t: any) => t.category === 'art9')
    expect(art9).toBeDefined()
    expect((art9 as any).minimumLevel).toBe('L3')
  })

  it('minors answer sets requiresDSFA = true', () => {
    const triggers = complianceScopeEngine.evaluateHardTriggers([
      ans('data_minors', true),
    ])
    const minorsTrigger = triggers.find((t: any) => t.category === 'vulnerable')
    expect(minorsTrigger).toBeDefined()
    expect((minorsTrigger as any).requiresDSFA).toBe(true)
  })

  it('AI scoring usage answer triggers minimumLevel L2 (adm category)', () => {
    const triggers = complianceScopeEngine.evaluateHardTriggers([
      ans('proc_ai_usage', ['scoring']),
    ])
    const aiTrigger = triggers.find((t: any) => t.category === 'adm')
    expect(aiTrigger).toBeDefined()
    expect(['L2', 'L3', 'L4']).toContain((aiTrigger as any).minimumLevel)
  })

  it('multiple triggers all returned when multiple questions answered', () => {
    const triggers = complianceScopeEngine.evaluateHardTriggers([
      ans('data_art9', ['gesundheit']),
      ans('data_minors', true),
    ])
    expect(triggers.length).toBeGreaterThanOrEqual(2)
  })

  it('false answer for boolean trigger does not fire it', () => {
    const triggersBefore = complianceScopeEngine.evaluateHardTriggers([])
    const triggersWithFalse = complianceScopeEngine.evaluateHardTriggers([
      ans('data_minors', false),
    ])
    expect(triggersWithFalse.length).toBe(triggersBefore.length)
  })
})

// ============================================================================
// evaluate — integration
// ============================================================================

describe('evaluate — integration', () => {
  it('empty answers → L1 ScopeDecision with all required fields', () => {
    const decision = complianceScopeEngine.evaluate([])
    expect(decision.scores).toBeDefined()
    expect(decision.triggeredHardTriggers).toBeDefined()
    expect(decision.requiredDocuments).toBeDefined()
    expect(decision.riskFlags).toBeDefined()
    expect(decision.gaps).toBeDefined()
    expect(decision.nextActions).toBeDefined()
    expect(decision.reasoning).toBeDefined()
    expect(decision.determinedLevel).toBe('L1')
    expect(decision.evaluatedAt).toBeDefined()
  })

  it('art9 + minors answers → determinedLevel L3 or L4', () => {
    const decision = complianceScopeEngine.evaluate([
      ans('data_art9', ['gesundheit', 'biometrie']),
      ans('data_minors', true),
    ])
    expect(['L3', 'L4']).toContain(decision.determinedLevel)
  })

  it('triggeredHardTriggers populated on evaluate with art9 answer', () => {
    const decision = complianceScopeEngine.evaluate([
      ans('data_art9', ['gesundheit']),
    ])
    expect(decision.triggeredHardTriggers.length).toBeGreaterThan(0)
  })

  it('composite score non-negative', () => {
    const decision = complianceScopeEngine.evaluate([ans('org_employee_count', '50-249')])
    expect((decision.scores as any).composite_score).toBeGreaterThanOrEqual(0)
  })

  it('evaluate returns array types for collections', () => {
    const decision = complianceScopeEngine.evaluate([])
    expect(Array.isArray(decision.triggeredHardTriggers)).toBe(true)
    expect(Array.isArray(decision.requiredDocuments)).toBe(true)
    expect(Array.isArray(decision.riskFlags)).toBe(true)
    expect(Array.isArray(decision.gaps)).toBe(true)
    expect(Array.isArray(decision.nextActions)).toBe(true)
  })
})

// ============================================================================
// buildDocumentScope
// ============================================================================

describe('buildDocumentScope', () => {
  it('returns an array', () => {
    const docs = complianceScopeEngine.buildDocumentScope('L1', [], [])
    expect(Array.isArray(docs)).toBe(true)
  })

  it('each returned document has documentType, label, estimatedEffort', () => {
    // Use a trigger with uppercase docType to test engine behaviour
    const t = trigger('HT-A01', 'L3', {
      category: 'art9',
      mandatoryDocuments: ['VVT'],
    })
    const docs = complianceScopeEngine.buildDocumentScope('L3', [t], [])
    docs.forEach((doc: any) => {
      expect(doc.documentType).toBeDefined()
      expect(doc.label).toBeDefined()
      expect(typeof doc.estimatedEffort).toBe('number')
      expect(doc.estimatedEffort).toBeGreaterThan(0)
    })
  })

  it('trigger-mandated documents get high priority', () => {
    const t = trigger('HT-A01', 'L3', {
      category: 'art9',
      mandatoryDocuments: ['VVT'],
    })
    const docs = complianceScopeEngine.buildDocumentScope('L3', [t], [])
    // A document required only by the level matrix is mandatory but may be
    // medium priority (getDocumentPriority); only trigger-mandated docs (those
    // with a populated triggeredBy) are forced to high priority.
    const triggerMandated = docs.filter((d: any) => (d.triggeredBy?.length ?? 0) > 0)
    expect(triggerMandated.length).toBeGreaterThan(0)
    triggerMandated.forEach((doc: any) => {
      expect(doc.priority).toBe('high')
    })
  })

  it('normalizes UPPERCASE trigger doc names to lowercase ScopeDocumentType', () => {
    const t = trigger('HT-test', 'L2', {
      category: 'test',
      mandatoryDocuments: ['VVT', 'TOM', 'DSFA'],
    })
    const docs = complianceScopeEngine.buildDocumentScope('L2', [t], [])
    const vvt = docs.find((d: any) => d.documentType === 'vvt')
    const tom = docs.find((d: any) => d.documentType === 'tom')
    const dsfa = docs.find((d: any) => d.documentType === 'dsfa')
    expect(vvt).toBeDefined()
    expect(vvt!.requirement).toBe('mandatory')
    expect(vvt!.triggeredBy).toContain('HT-test')
    expect(tom).toBeDefined()
    expect(tom!.requirement).toBe('mandatory')
    expect(dsfa).toBeDefined()
    expect(dsfa!.requirement).toBe('mandatory')
  })

  it('normalizes aliased doc names (DSE→dsi, LOESCHKONZEPT→lf)', () => {
    const t = trigger('HT-alias', 'L2', {
      category: 'test',
      mandatoryDocuments: ['DSE', 'LOESCHKONZEPT', 'DSR_PROZESS'],
    })
    const docs = complianceScopeEngine.buildDocumentScope('L2', [t], [])
    const dsi = docs.find((d: any) => d.documentType === 'dsi')
    const lf = docs.find((d: any) => d.documentType === 'lf')
    const betroffenenrechte = docs.find((d: any) => d.documentType === 'betroffenenrechte')
    expect(dsi).toBeDefined()
    expect(dsi!.requirement).toBe('mandatory')
    expect(dsi!.triggeredBy).toContain('HT-alias')
    expect(lf).toBeDefined()
    expect(lf!.requirement).toBe('mandatory')
    expect(betroffenenrechte).toBeDefined()
    expect(betroffenenrechte!.requirement).toBe('mandatory')
  })

  it('documents sorted: mandatory first', () => {
    const decision = complianceScopeEngine.evaluate([
      ans('data_art9', ['gesundheit']),
    ])
    const docs = decision.requiredDocuments
    if (docs.length > 1) {
      // mandatory docs should appear before recommended ones
      let seenNonMandatory = false
      for (const doc of docs) {
        if ((doc as any).requirement !== 'mandatory') seenNonMandatory = true
        if (seenNonMandatory) {
          expect((doc as any).requirement).not.toBe('mandatory')
        }
      }
    }
  })

  it('sdkStepUrl present for known document types when available', () => {
    const decision = complianceScopeEngine.evaluate([
      ans('data_art9', ['gesundheit']),
    ])
    // sdkStepUrl is optional — just verify it's a string when present
    decision.requiredDocuments.forEach((doc: any) => {
      if (doc.sdkStepUrl !== undefined) {
        expect(typeof doc.sdkStepUrl).toBe('string')
      }
    })
  })
})

// ============================================================================
// evaluateRiskFlags
// ============================================================================

describe('evaluateRiskFlags', () => {
  it('no answers → empty flags array', () => {
    const flags = complianceScopeEngine.evaluateRiskFlags([], 'L1')
    expect(Array.isArray(flags)).toBe(true)
  })

  it('no encryption transit at L2+ → high risk flag (technical)', () => {
    const flags = complianceScopeEngine.evaluateRiskFlags(
      [ans('tech_encryption_transit', false)],
      'L2',
    )
    const encFlag = flags.find((f: any) => f.category === 'technical')
    expect(encFlag).toBeDefined()
    expect((encFlag as any).severity).toBe('high')
  })

  it('no encryption rest at L2+ → high risk flag (technical)', () => {
    const flags = complianceScopeEngine.evaluateRiskFlags(
      [ans('tech_encryption_rest', false)],
      'L2',
    )
    const encFlag = flags.find((f: any) => f.category === 'technical')
    expect(encFlag).toBeDefined()
    expect((encFlag as any).severity).toBe('high')
  })

  it('encryption flags not raised at L1', () => {
    const flags = complianceScopeEngine.evaluateRiskFlags(
      [ans('tech_encryption_transit', false)],
      'L1',
    )
    const encFlag = flags.find((f: any) => f.message?.includes('Verschlüsselung'))
    expect(encFlag).toBeUndefined()
  })

  it('third country transfer without guarantees → legal flag', () => {
    const flags = complianceScopeEngine.evaluateRiskFlags(
      [
        ans('tech_third_country', true),
        ans('tech_hosting_location', 'drittland'),
      ],
      'L1',
    )
    const legalFlag = flags.find((f: any) => f.category === 'legal')
    expect(legalFlag).toBeDefined()
  })

  it('≥250 employees without DPO → organizational flag', () => {
    const flags = complianceScopeEngine.evaluateRiskFlags(
      [
        ans('org_has_dpo', false),
        ans('org_employee_count', '250-999'),
      ],
      'L2',
    )
    const orgFlag = flags.find((f: any) => f.category === 'organizational')
    expect(orgFlag).toBeDefined()
  })

  it('DPO present with 250+ employees → no DPO flag', () => {
    const flags = complianceScopeEngine.evaluateRiskFlags(
      [
        ans('org_has_dpo', true),
        ans('org_employee_count', '250-999'),
      ],
      'L2',
    )
    const orgFlag = flags.find((f: any) => f.category === 'organizational')
    expect(orgFlag).toBeUndefined()
  })
})

// ============================================================================
// HT-H01a/b: B2B vs B2C Webshop Trigger Split
// ============================================================================

describe('HT-H01a/b — B2B vs B2C Webshop', () => {
  it('B2C webshop triggers HT-H01a with Verbraucherschutz documents', () => {
    const triggers = complianceScopeEngine.evaluateHardTriggers([
      ans('prod_webshop', true),
      ans('org_business_model', 'B2C'),
    ])
    const h01a = triggers.find((t: any) => t.ruleId === 'HT-H01a')
    const h01b = triggers.find((t: any) => t.ruleId === 'HT-H01b')
    expect(h01a).toBeDefined()
    expect(h01b).toBeUndefined()
    expect(h01a!.mandatoryDocuments).toContain('WIDERRUFSBELEHRUNG')
    expect(h01a!.mandatoryDocuments).toContain('PREISANGABEN')
    expect(h01a!.mandatoryDocuments).toContain('FERNABSATZ_INFO')
    expect(h01a!.mandatoryDocuments).toContain('STREITBEILEGUNG')
  })

  it('B2B webshop triggers HT-H01b without Verbraucherschutz documents', () => {
    const triggers = complianceScopeEngine.evaluateHardTriggers([
      ans('prod_webshop', true),
      ans('org_business_model', 'B2B'),
    ])
    const h01a = triggers.find((t: any) => t.ruleId === 'HT-H01a')
    const h01b = triggers.find((t: any) => t.ruleId === 'HT-H01b')
    expect(h01a).toBeUndefined()
    expect(h01b).toBeDefined()
    expect(h01b!.mandatoryDocuments).toContain('DSE')
    expect(h01b!.mandatoryDocuments).toContain('AGB')
    expect(h01b!.mandatoryDocuments).toContain('COOKIE_BANNER')
    expect(h01b!.mandatoryDocuments).not.toContain('WIDERRUFSBELEHRUNG')
    expect(h01b!.mandatoryDocuments).not.toContain('PREISANGABEN')
  })

  it('B2B_B2C (hybrid) webshop triggers HT-H01a (Verbraucherschutz applies)', () => {
    const triggers = complianceScopeEngine.evaluateHardTriggers([
      ans('prod_webshop', true),
      ans('org_business_model', 'B2B_B2C'),
    ])
    const h01a = triggers.find((t: any) => t.ruleId === 'HT-H01a')
    const h01b = triggers.find((t: any) => t.ruleId === 'HT-H01b')
    expect(h01a).toBeDefined()
    expect(h01b).toBeUndefined()
  })

  it('no webshop → neither HT-H01a nor HT-H01b fires', () => {
    const triggers = complianceScopeEngine.evaluateHardTriggers([
      ans('prod_webshop', false),
      ans('org_business_model', 'B2C'),
    ])
    const h01a = triggers.find((t: any) => t.ruleId === 'HT-H01a')
    const h01b = triggers.find((t: any) => t.ruleId === 'HT-H01b')
    expect(h01a).toBeUndefined()
    expect(h01b).toBeUndefined()
  })

  it('webshop without business_model answer → HT-H01a fires (excludeWhen not matched)', () => {
    const triggers = complianceScopeEngine.evaluateHardTriggers([
      ans('prod_webshop', true),
    ])
    const h01a = triggers.find((t: any) => t.ruleId === 'HT-H01a')
    const h01b = triggers.find((t: any) => t.ruleId === 'HT-H01b')
    // excludeWhen B2B: not matched (undefined !== 'B2B') → fires
    expect(h01a).toBeDefined()
    // requireWhen B2B: not matched (undefined !== 'B2B') → does not fire
    expect(h01b).toBeUndefined()
  })
})

// ============================================================================
// excludeWhen / requireWhen Logic (unit)
// ============================================================================

describe('excludeWhen / requireWhen — generic logic', () => {
  it('excludeWhen with array value excludes any matching value', () => {
    // HT-H01a has excludeWhen: { questionId: 'org_business_model', value: 'B2B' }
    // This test verifies the single-value case works (B2B excluded)
    const triggers = complianceScopeEngine.evaluateHardTriggers([
      ans('prod_webshop', true),
      ans('org_business_model', 'B2B'),
    ])
    const h01a = triggers.find((t: any) => t.ruleId === 'HT-H01a')
    expect(h01a).toBeUndefined()
  })

  it('requireWhen with non-matching value prevents trigger', () => {
    // HT-H01b has requireWhen: { questionId: 'org_business_model', value: 'B2B' }
    const triggers = complianceScopeEngine.evaluateHardTriggers([
      ans('prod_webshop', true),
      ans('org_business_model', 'B2C'),
    ])
    const h01b = triggers.find((t: any) => t.ruleId === 'HT-H01b')
    expect(h01b).toBeUndefined()
  })
})
