import { describe, expect, it } from 'vitest'
import { IdentityDomainSchema } from '../../domains/identity'
import { resolveOrganizationModel } from '../../resolve'

/**
 * Profile-singleton domains (`branding`, `identity`) use `.passthrough()` so that
 * tenant-authored direct properties survive `resolveOrganizationModel` instead of
 * being silently stripped by Zod's default `.strip()`. These tests lock that
 * behavior in and verify the broadened typed branding fields round-trip.
 */
describe('profile-singleton passthrough extensibility', () => {
  it('keeps an unknown direct property on branding through resolve', () => {
    const model = resolveOrganizationModel({
      branding: {
        organizationName: 'Acme',
        productName: 'AcmeOS',
        shortName: 'Acme',
        // Unknown tenant-authored direct property — must survive passthrough.
        brandVibe: 'warm'
      } as never
    })

    expect((model.branding as Record<string, unknown>).brandVibe).toBe('warm')
    // Known fields still resolve normally.
    expect(model.branding.organizationName).toBe('Acme')
  })

  it('keeps an unknown direct property on identity through resolve', () => {
    const model = resolveOrganizationModel({
      identity: {
        // Unknown tenant-authored direct property — must survive passthrough.
        founderNote: 'bootstrapped'
      } as never
    })

    expect((model.identity as Record<string, unknown>).founderNote).toBe('bootstrapped')
    // Known identity fields still resolve from defaults.
    expect(model.identity.timeZone).toBe('UTC')
  })

  it('parses and round-trips the broadened typed branding fields', () => {
    const model = resolveOrganizationModel({
      branding: {
        organizationName: 'Acme',
        productName: 'AcmeOS',
        shortName: 'Acme',
        voice: 'Direct and human — no jargon',
        tagline: 'Automation that works while you sleep',
        values: ['Transparency', 'Craftsmanship', 'Velocity'],
        themePresetId: 'midnight'
      }
    })

    expect(model.branding.voice).toBe('Direct and human — no jargon')
    expect(model.branding.tagline).toBe('Automation that works while you sleep')
    expect(model.branding.values).toEqual(['Transparency', 'Craftsmanship', 'Velocity'])
    expect(model.branding.themePresetId).toBe('midnight')
  })

  it('leaves the broadened branding fields undefined when not authored', () => {
    const model = resolveOrganizationModel()

    expect(model.branding.voice).toBeUndefined()
    expect(model.branding.tagline).toBeUndefined()
    expect(model.branding.values).toBeUndefined()
    expect(model.branding.themePresetId).toBeUndefined()
  })
})

/**
 * Names → identity: the four display-name fields (organizationName, productName,
 * shortName, description) are now the recommended placement on `identity`. They are
 * optional so that legacy tenants can continue to rely on `branding.*` fallbacks
 * without breaking. These tests lock the contract: fields round-trip, are truly
 * optional (no default), and branding fields still validate unchanged.
 */
describe('identity display-name fields — recommended placement (Step 5)', () => {
  it('accepts all four name fields on identity and round-trips them through resolveOrganizationModel', () => {
    const model = resolveOrganizationModel({
      identity: {
        organizationName: 'Acme Corp',
        productName: 'AcmeOS',
        shortName: 'Acme',
        description: 'AI orchestration for modern teams.'
      }
    })

    expect(model.identity.organizationName).toBe('Acme Corp')
    expect(model.identity.productName).toBe('AcmeOS')
    expect(model.identity.shortName).toBe('Acme')
    expect(model.identity.description).toBe('AI orchestration for modern teams.')
  })

  it('identity name fields are optional — a model with none set still resolves, and resolved values are undefined', () => {
    const model = resolveOrganizationModel()

    expect(model.identity.organizationName).toBeUndefined()
    expect(model.identity.productName).toBeUndefined()
    expect(model.identity.shortName).toBeUndefined()
    expect(model.identity.description).toBeUndefined()
  })

  it('identity name fields parse as undefined through IdentityDomainSchema directly (no defaults injected)', () => {
    const result = IdentityDomainSchema.safeParse({})
    expect(result.success).toBe(true)
    if (result.success) {
      expect(result.data.organizationName).toBeUndefined()
      expect(result.data.productName).toBeUndefined()
      expect(result.data.shortName).toBeUndefined()
      expect(result.data.description).toBeUndefined()
    }
  })

  it('identity.shortName rejects values exceeding 40 characters', () => {
    expect(() =>
      resolveOrganizationModel({
        identity: { shortName: 'a'.repeat(41) } as never
      })
    ).toThrow()
  })

  it('identity.organizationName rejects empty string (LabelSchema min 1)', () => {
    expect(() =>
      resolveOrganizationModel({
        identity: { organizationName: '' } as never
      })
    ).toThrow()
  })

  it('identity.description rejects empty string (DescriptionSchema min 1)', () => {
    expect(() =>
      resolveOrganizationModel({
        identity: { description: '' } as never
      })
    ).toThrow()
  })

  it('identity name fields trim whitespace', () => {
    const model = resolveOrganizationModel({
      identity: {
        organizationName: '  Acme Corp  ',
        shortName: '  Acme  '
      } as never
    })

    expect(model.identity.organizationName).toBe('Acme Corp')
    expect(model.identity.shortName).toBe('Acme')
  })

  it('branding still validates with its name fields — @deprecated tags change nothing at runtime', () => {
    const model = resolveOrganizationModel({
      branding: {
        organizationName: 'Legacy Org',
        productName: 'LegacyOS',
        shortName: 'LGO',
        description: 'Legacy description for back-compat.'
      }
    })

    expect(model.branding.organizationName).toBe('Legacy Org')
    expect(model.branding.productName).toBe('LegacyOS')
    expect(model.branding.shortName).toBe('LGO')
    expect(model.branding.description).toBe('Legacy description for back-compat.')
  })
})

/**
 * The broadened branding fields are *typed*, not passthrough — their constraints
 * are part of the published contract. These tests lock those bounds so a future
 * edit can't silently widen/drop them, and confirm `.passthrough()` does NOT
 * bypass validation for known fields (only unknown ones are let through).
 */
describe('broadened branding typed-field validation', () => {
  it('rejects a voice longer than 280 characters', () => {
    expect(() => resolveOrganizationModel({ branding: { voice: 'a'.repeat(281) } as never })).toThrow()
  })

  it('rejects a tagline longer than 200 characters', () => {
    expect(() => resolveOrganizationModel({ branding: { tagline: 'a'.repeat(201) } as never })).toThrow()
  })

  it('rejects an empty-string entry in values (each entry must be non-empty)', () => {
    expect(() => resolveOrganizationModel({ branding: { values: ['Valid', ''] } as never })).toThrow()
  })

  it('rejects a themePresetId outside the 1–64 character bounds', () => {
    expect(() => resolveOrganizationModel({ branding: { themePresetId: '' } as never })).toThrow()
    expect(() => resolveOrganizationModel({ branding: { themePresetId: 'a'.repeat(65) } as never })).toThrow()
  })

  it('trims whitespace on string fields', () => {
    const model = resolveOrganizationModel({
      branding: { voice: '  trimmed voice  ', tagline: '  trimmed tagline  ' } as never
    })

    expect(model.branding.voice).toBe('trimmed voice')
    expect(model.branding.tagline).toBe('trimmed tagline')
  })
})
