import { describe, expect, it } from 'vitest'
import {
  ExternalSyncCategorySchema,
  ExternalSyncDeletePolicySchema,
  ExternalSyncOwnerSchema,
  ExternalSyncStrategySchema,
  ScaffoldEntryKindSchema,
  ScaffoldRegistryEntrySchema,
  ScaffoldRegistrySchema
} from '../schema'

// ---------------------------------------------------------------------------
// Kind enum
// ---------------------------------------------------------------------------

describe('ScaffoldEntryKindSchema', () => {
  it('accepts all declared kinds', () => {
    const validKinds = [
      'autogen',
      'manual-scaffold',
      'sync-preservation',
      'validator',
      'other'
    ]
    for (const kind of validKinds) {
      expect(ScaffoldEntryKindSchema.safeParse(kind).success).toBe(true)
    }
  })

  it('rejects unknown kinds', () => {
    const result = ScaffoldEntryKindSchema.safeParse('unknown-kind')
    expect(result.success).toBe(false)
  })

  it('rejects empty string', () => {
    expect(ScaffoldEntryKindSchema.safeParse('').success).toBe(false)
  })
})

describe('External sync enums', () => {
  it('accepts all declared external sync categories', () => {
    for (const category of ['replace', 'merge', 'never-touch', 'generated']) {
      expect(ExternalSyncCategorySchema.safeParse(category).success).toBe(true)
    }
  })

  it('accepts all declared external sync owners', () => {
    for (const owner of ['template', '@elevasis/ui', '@elevasis/core', '@elevasis/sdk', 'generator', 'project']) {
      expect(ExternalSyncOwnerSchema.safeParse(owner).success).toBe(true)
    }
  })

  it('accepts all declared external sync strategies', () => {
    for (const strategy of [
      'replace-all',
      'merge-regions',
      'merge-baseline',
      'verify-only',
      'generated-freshness'
    ]) {
      expect(ExternalSyncStrategySchema.safeParse(strategy).success).toBe(true)
    }
  })

  it('accepts all declared external sync delete policies', () => {
    for (const deletePolicy of ['none', 'manifest-only']) {
      expect(ExternalSyncDeletePolicySchema.safeParse(deletePolicy).success).toBe(true)
    }
  })

  it('rejects unknown external sync enum values', () => {
    expect(ExternalSyncCategorySchema.safeParse('rewrite').success).toBe(false)
    expect(ExternalSyncOwnerSchema.safeParse('packages/core').success).toBe(false)
    expect(ExternalSyncStrategySchema.safeParse('replace-some').success).toBe(false)
    expect(ExternalSyncDeletePolicySchema.safeParse('delete-all').success).toBe(false)
  })
})

// ---------------------------------------------------------------------------
// Valid full entry
// ---------------------------------------------------------------------------

const validEntry = {
  id: 'business-directory-rename',
  kind: 'manual-scaffold',
  owner: 'packages/core',
  sources: ['packages/core/src/business/**'],
  dependents: [
    {
      path: '.navigation/_generated/skeleton.md',
      regen: 'pnpm navigation:generate',
      hint: 'Regenerate after rename.'
    }
  ]
}

describe('ScaffoldRegistryEntrySchema — valid entry', () => {
  it('parses a complete valid entry', () => {
    const result = ScaffoldRegistryEntrySchema.safeParse(validEntry)
    expect(result.success).toBe(true)
    if (result.success) {
      expect(result.data.id).toBe('business-directory-rename')
      expect(result.data.kind).toBe('manual-scaffold')
      expect(result.data.sources).toHaveLength(1)
      expect(result.data.dependents).toHaveLength(1)
    }
  })

  it('accepts optional notes and cooldown_ms', () => {
    const withOptionals = {
      ...validEntry,
      notes: 'Extra context here.',
      cooldown_ms: 60_000
    }
    const result = ScaffoldRegistryEntrySchema.safeParse(withOptionals)
    expect(result.success).toBe(true)
  })

  it('accepts optional regen command when provided', () => {
    const withRegen = { ...validEntry, regen: 'pnpm navigation:generate' }
    const result = ScaffoldRegistryEntrySchema.safeParse(withRegen)
    expect(result.success).toBe(true)
    if (result.success) {
      expect(result.data.regen).toBe('pnpm navigation:generate')
    }
  })

  it('accepts entry without regen field (optional)', () => {
    const result = ScaffoldRegistryEntrySchema.safeParse(validEntry)
    expect(result.success).toBe(true)
    if (result.success) {
      expect(result.data.regen).toBeUndefined()
    }
  })

  it('rejects regen as empty string', () => {
    const withEmptyRegen = { ...validEntry, regen: '' }
    const result = ScaffoldRegistryEntrySchema.safeParse(withEmptyRegen)
    expect(result.success).toBe(false)
  })

  it('rejects regen as non-string (number)', () => {
    const withBadRegen = { ...validEntry, regen: 42 }
    const result = ScaffoldRegistryEntrySchema.safeParse(withBadRegen)
    expect(result.success).toBe(false)
  })

  it('accepts entry without optional hint / regen in dependent', () => {
    const minimalDependent = {
      ...validEntry,
      dependents: [{ path: 'some/path' }]
    }
    expect(ScaffoldRegistryEntrySchema.safeParse(minimalDependent).success).toBe(true)
  })

  it('accepts auto_regen: true', () => {
    const withAutoRegen = { ...validEntry, auto_regen: true }
    expect(ScaffoldRegistryEntrySchema.safeParse(withAutoRegen).success).toBe(true)
  })

  it('accepts auto_regen: false', () => {
    const withAutoRegen = { ...validEntry, auto_regen: false }
    expect(ScaffoldRegistryEntrySchema.safeParse(withAutoRegen).success).toBe(true)
  })

  it('accepts entry without auto_regen (optional)', () => {
    expect(ScaffoldRegistryEntrySchema.safeParse(validEntry).success).toBe(true)
  })

  it('rejects auto_regen as a non-boolean', () => {
    const withBadAutoRegen = { ...validEntry, auto_regen: 'yes' }
    expect(ScaffoldRegistryEntrySchema.safeParse(withBadAutoRegen).success).toBe(false)
  })

  it('parses all seed-entry kinds with required invariants', () => {
    const entries = [
      { ...validEntry, kind: 'autogen', regen: 'pnpm navigation:generate' },
      { ...validEntry, kind: 'manual-scaffold' },
      {
        ...validEntry,
        kind: 'sync-preservation',
        owner: 'template',
        category: 'replace',
        strategy: 'replace-all',
        delete_policy: 'none'
      },
      { ...validEntry, kind: 'validator', dependents: [{ path: '(self)', regen: 'pnpm meta:verify' }] },
      { ...validEntry, kind: 'other', notes: 'Vendor generated.' }
    ]
    for (const entry of entries) {
      const result = ScaffoldRegistryEntrySchema.safeParse(entry)
      const kind = entry.kind
      expect(result.success, `kind ${kind} should parse`).toBe(true)
    }
  })

  it('accepts optional external sync fields for external-sync-aware entries', () => {
    const withExternalSyncFields = {
      ...validEntry,
      owner: 'template',
      category: 'replace',
      strategy: 'replace-all',
      delete_policy: 'manifest-only'
    }
    const result = ScaffoldRegistryEntrySchema.safeParse(withExternalSyncFields)
    expect(result.success).toBe(true)
    if (result.success) {
      expect(result.data.category).toBe('replace')
      expect(result.data.strategy).toBe('replace-all')
      expect(result.data.delete_policy).toBe('manifest-only')
    }
  })

  it('rejects autogen entries without top-level regen', () => {
    const result = ScaffoldRegistryEntrySchema.safeParse({ ...validEntry, kind: 'autogen' })
    expect(result.success).toBe(false)
  })

  it('rejects other entries without notes', () => {
    const result = ScaffoldRegistryEntrySchema.safeParse({ ...validEntry, kind: 'other' })
    expect(result.success).toBe(false)
  })

  it('rejects sync-preservation entries without external sync metadata', () => {
    const result = ScaffoldRegistryEntrySchema.safeParse({ ...validEntry, kind: 'sync-preservation' })
    expect(result.success).toBe(false)
  })

  it('keeps legacy free-form owner values valid when external sync fields are omitted', () => {
    const result = ScaffoldRegistryEntrySchema.safeParse({
      ...validEntry,
      owner: 'packages/core'
    })
    expect(result.success).toBe(true)
  })
})

// ---------------------------------------------------------------------------
// Invalid entries — required fields missing
// ---------------------------------------------------------------------------

describe('ScaffoldRegistryEntrySchema — invalid entries', () => {
  it('rejects entry missing id', () => {
    const { id: _id, ...withoutId } = validEntry
    expect(ScaffoldRegistryEntrySchema.safeParse(withoutId).success).toBe(false)
  })

  it('rejects entry missing kind', () => {
    const { kind: _kind, ...withoutKind } = validEntry
    expect(ScaffoldRegistryEntrySchema.safeParse(withoutKind).success).toBe(false)
  })

  it('rejects entry missing owner', () => {
    const { owner: _owner, ...withoutOwner } = validEntry
    expect(ScaffoldRegistryEntrySchema.safeParse(withoutOwner).success).toBe(false)
  })

  it('rejects entry missing sources', () => {
    const { sources: _sources, ...withoutSources } = validEntry
    expect(ScaffoldRegistryEntrySchema.safeParse(withoutSources).success).toBe(false)
  })

  it('rejects entry missing dependents', () => {
    const { dependents: _dependents, ...withoutDependents } = validEntry
    expect(ScaffoldRegistryEntrySchema.safeParse(withoutDependents).success).toBe(false)
  })

  it('rejects invalid kind', () => {
    const withBadKind = { ...validEntry, kind: 'not-a-real-kind' }
    const result = ScaffoldRegistryEntrySchema.safeParse(withBadKind)
    expect(result.success).toBe(false)
  })

  it('rejects id with uppercase characters', () => {
    const withBadId = { ...validEntry, id: 'BusinessDirectoryRename' }
    expect(ScaffoldRegistryEntrySchema.safeParse(withBadId).success).toBe(false)
  })

  it('rejects id with spaces', () => {
    const withBadId = { ...validEntry, id: 'business directory rename' }
    expect(ScaffoldRegistryEntrySchema.safeParse(withBadId).success).toBe(false)
  })

  it('rejects empty sources array', () => {
    const withEmptySources = { ...validEntry, sources: [] }
    expect(ScaffoldRegistryEntrySchema.safeParse(withEmptySources).success).toBe(false)
  })

  it('rejects empty dependents array', () => {
    const withEmptyDependents = { ...validEntry, dependents: [] }
    expect(ScaffoldRegistryEntrySchema.safeParse(withEmptyDependents).success).toBe(false)
  })

  it('rejects dependent missing path', () => {
    const withBadDependent = {
      ...validEntry,
      dependents: [{ regen: 'pnpm nav' }]
    }
    expect(ScaffoldRegistryEntrySchema.safeParse(withBadDependent).success).toBe(false)
  })

  it('rejects negative cooldown_ms', () => {
    const withBadCooldown = { ...validEntry, cooldown_ms: -1 }
    expect(ScaffoldRegistryEntrySchema.safeParse(withBadCooldown).success).toBe(false)
  })

  it('rejects non-integer cooldown_ms', () => {
    const withBadCooldown = { ...validEntry, cooldown_ms: 300.5 }
    expect(ScaffoldRegistryEntrySchema.safeParse(withBadCooldown).success).toBe(false)
  })

  it('rejects invalid external sync category', () => {
    const withBadCategory = { ...validEntry, owner: 'template', category: 'rewrite' }
    expect(ScaffoldRegistryEntrySchema.safeParse(withBadCategory).success).toBe(false)
  })

  it('rejects invalid external sync strategy', () => {
    const withBadStrategy = { ...validEntry, owner: 'template', strategy: 'replace-some' }
    expect(ScaffoldRegistryEntrySchema.safeParse(withBadStrategy).success).toBe(false)
  })

  it('rejects invalid external sync delete policy', () => {
    const withBadDeletePolicy = { ...validEntry, owner: 'template', delete_policy: 'delete-all' }
    expect(ScaffoldRegistryEntrySchema.safeParse(withBadDeletePolicy).success).toBe(false)
  })

  it('rejects legacy owner strings when external sync metadata is present', () => {
    const withLegacyOwner = {
      ...validEntry,
      owner: 'external/_template',
      category: 'replace',
      strategy: 'replace-all',
      delete_policy: 'manifest-only'
    }
    const result = ScaffoldRegistryEntrySchema.safeParse(withLegacyOwner)
    expect(result.success).toBe(false)
  })
})

// ---------------------------------------------------------------------------
// Top-level registry document
// ---------------------------------------------------------------------------

describe('ScaffoldRegistrySchema', () => {
  const validRegistry = {
    version: '1',
    entries: [validEntry]
  }

  it('parses a valid registry document', () => {
    const result = ScaffoldRegistrySchema.safeParse(validRegistry)
    expect(result.success).toBe(true)
    if (result.success) {
      expect(result.data.version).toBe('1')
      expect(result.data.entries).toHaveLength(1)
    }
  })

  it('applies default version when omitted', () => {
    const withoutVersion = { entries: [validEntry] }
    const result = ScaffoldRegistrySchema.safeParse(withoutVersion)
    expect(result.success).toBe(true)
    if (result.success) {
      expect(result.data.version).toBe('1')
    }
  })

  it('rejects registry with no entries', () => {
    const empty = { version: '1', entries: [] }
    expect(ScaffoldRegistrySchema.safeParse(empty).success).toBe(false)
  })

  it('rejects registry missing entries key', () => {
    expect(ScaffoldRegistrySchema.safeParse({ version: '1' }).success).toBe(false)
  })

  it('accepts multiple entries of different kinds', () => {
    const multiEntry = {
      version: '1',
      entries: [
        validEntry,
        {
          id: 'meta-json-updated',
          kind: 'validator',
          owner: 'apps/docs',
          sources: ['apps/docs/content/docs/**/meta.json'],
          dependents: [{ path: '(self)', regen: 'pnpm check-docs-meta' }]
        },
        {
          id: 'external-template-hook-added',
          kind: 'sync-preservation',
          owner: 'template',
          category: 'replace',
          strategy: 'replace-all',
          delete_policy: 'none',
          sources: ['external/_template/.claude/hooks/*.mjs'],
          dependents: [{ path: 'external/_template/.claude/settings.json', regen: 'manual' }]
        }
      ]
    }
    const result = ScaffoldRegistrySchema.safeParse(multiEntry)
    expect(result.success).toBe(true)
    if (result.success) {
      expect(result.data.entries).toHaveLength(3)
    }
  })
})
