/**
 * Base Entity Contracts - Unit Tests
 *
 * Verifies that:
 * - Base interfaces accept generic TMeta narrowing
 * - Zod schemas parse valid data correctly
 * - Schema extension via .extend() works for client metadata narrowing
 * - Default metadata falls back to Record<string, unknown>
 */

import { describe, it, expect } from 'vitest'
import { z } from 'zod'
import {
  BaseProjectSchema,
  BaseMilestoneSchema,
  BaseTaskSchema,
  BaseDealSchema,
  BaseCompanySchema,
  BaseContactSchema,
  type BaseProject,
  type BaseMilestone,
  type BaseTask,
  type BaseDeal,
  type BaseCompany,
  type BaseContact
} from './base-entities'

// ---------------------------------------------------------------------------
// Fixtures
// ---------------------------------------------------------------------------

const ORG_ID = '00000000-0000-0000-0000-000000000001'
const TIMESTAMPS = {
  createdAt: '2026-04-17T00:00:00.000Z',
  updatedAt: '2026-04-17T00:00:00.000Z'
}

// ---------------------------------------------------------------------------
// BaseProjectSchema
// ---------------------------------------------------------------------------

describe('BaseProjectSchema', () => {
  const validProject = {
    id: '00000000-0000-0000-0000-000000000010',
    organizationId: ORG_ID,
    name: 'Website Rebuild',
    kind: 'client_engagement',
    status: 'active',
    description: 'Rebuild the client website.',
    metadata: { budget: 50000 },
    ...TIMESTAMPS
  }

  it('parses a valid project', () => {
    const result = BaseProjectSchema.safeParse(validProject)
    expect(result.success).toBe(true)
    if (result.success) {
      expect(result.data.name).toBe('Website Rebuild')
      expect(result.data.kind).toBe('client_engagement')
      expect(result.data.status).toBe('active')
    }
  })

  it('accepts null description', () => {
    const result = BaseProjectSchema.safeParse({ ...validProject, description: null })
    expect(result.success).toBe(true)
  })

  it('defaults metadata to empty object when omitted', () => {
    const { metadata: _meta, ...withoutMeta } = validProject
    const result = BaseProjectSchema.safeParse(withoutMeta)
    expect(result.success).toBe(true)
    if (result.success) {
      expect(result.data.metadata).toEqual({})
    }
  })

  it('rejects missing required fields', () => {
    const result = BaseProjectSchema.safeParse({ id: 'x', organizationId: ORG_ID })
    expect(result.success).toBe(false)
  })

  it('supports schema extension for narrowed metadata', () => {
    const ProjectMetaSchema = z.object({ budget: z.number(), riskScore: z.enum(['low', 'medium', 'high']) })
    const NarrowedProjectSchema = BaseProjectSchema.extend({ metadata: ProjectMetaSchema })

    const result = NarrowedProjectSchema.safeParse({
      ...validProject,
      metadata: { budget: 75000, riskScore: 'low' }
    })
    expect(result.success).toBe(true)
    if (result.success) {
      expect(result.data.metadata.budget).toBe(75000)
      expect(result.data.metadata.riskScore).toBe('low')
    }
  })

  it('rejects invalid narrowed metadata via extended schema', () => {
    const ProjectMetaSchema = z.object({ budget: z.number() })
    const NarrowedProjectSchema = BaseProjectSchema.extend({ metadata: ProjectMetaSchema })

    const result = NarrowedProjectSchema.safeParse({
      ...validProject,
      metadata: { budget: 'not-a-number' }
    })
    expect(result.success).toBe(false)
  })
})

// ---------------------------------------------------------------------------
// BaseMilestoneSchema
// ---------------------------------------------------------------------------

describe('BaseMilestoneSchema', () => {
  const validMilestone = {
    id: '00000000-0000-0000-0000-000000000020',
    organizationId: ORG_ID,
    projectId: '00000000-0000-0000-0000-000000000010',
    name: 'Phase 1 Complete',
    status: 'upcoming',
    description: null,
    metadata: {},
    ...TIMESTAMPS
  }

  it('parses a valid milestone', () => {
    const result = BaseMilestoneSchema.safeParse(validMilestone)
    expect(result.success).toBe(true)
    if (result.success) {
      expect(result.data.projectId).toBe('00000000-0000-0000-0000-000000000010')
      expect(result.data.status).toBe('upcoming')
    }
  })

  it('defaults metadata to empty object when omitted', () => {
    const { metadata: _meta, ...withoutMeta } = validMilestone
    const result = BaseMilestoneSchema.safeParse(withoutMeta)
    expect(result.success).toBe(true)
    if (result.success) {
      expect(result.data.metadata).toEqual({})
    }
  })

  it('supports schema extension for narrowed metadata', () => {
    const MilestoneMetaSchema = z.object({ deliverables: z.array(z.string()) })
    const NarrowedSchema = BaseMilestoneSchema.extend({ metadata: MilestoneMetaSchema })

    const result = NarrowedSchema.safeParse({
      ...validMilestone,
      metadata: { deliverables: ['doc-1', 'doc-2'] }
    })
    expect(result.success).toBe(true)
    if (result.success) {
      expect(result.data.metadata.deliverables).toHaveLength(2)
    }
  })
})

// ---------------------------------------------------------------------------
// BaseTaskSchema
// ---------------------------------------------------------------------------

describe('BaseTaskSchema', () => {
  const validTask = {
    id: '00000000-0000-0000-0000-000000000030',
    organizationId: ORG_ID,
    projectId: '00000000-0000-0000-0000-000000000010',
    name: 'Write API docs',
    status: 'planned',
    type: 'documentation',
    description: 'Document all endpoints.',
    metadata: { priority: 'high' },
    ...TIMESTAMPS
  }

  it('parses a valid task', () => {
    const result = BaseTaskSchema.safeParse(validTask)
    expect(result.success).toBe(true)
    if (result.success) {
      expect(result.data.type).toBe('documentation')
      expect(result.data.status).toBe('planned')
    }
  })

  it('accepts null description', () => {
    const result = BaseTaskSchema.safeParse({ ...validTask, description: null })
    expect(result.success).toBe(true)
  })

  it('supports schema extension for narrowed metadata', () => {
    const TaskMetaSchema = z.object({ storyPoints: z.number().int(), sprint: z.string().optional() })
    const NarrowedSchema = BaseTaskSchema.extend({ metadata: TaskMetaSchema })

    const result = NarrowedSchema.safeParse({
      ...validTask,
      metadata: { storyPoints: 3, sprint: 'S4' }
    })
    expect(result.success).toBe(true)
    if (result.success) {
      expect(result.data.metadata.storyPoints).toBe(3)
      expect(result.data.metadata.sprint).toBe('S4')
    }
  })
})

// ---------------------------------------------------------------------------
// BaseDealSchema
// ---------------------------------------------------------------------------

describe('BaseDealSchema', () => {
  const validDeal = {
    id: '00000000-0000-0000-0000-000000000040',
    organizationId: ORG_ID,
    contactEmail: 'prospect@example.com',
    stage: 'proposal',
    metadata: {},
    ...TIMESTAMPS
  }

  it('parses a valid deal', () => {
    const result = BaseDealSchema.safeParse(validDeal)
    expect(result.success).toBe(true)
    if (result.success) {
      expect(result.data.contactEmail).toBe('prospect@example.com')
      expect(result.data.stage).toBe('proposal')
    }
  })

  it('accepts null stage', () => {
    const result = BaseDealSchema.safeParse({ ...validDeal, stage: null })
    expect(result.success).toBe(true)
    if (result.success) {
      expect(result.data.stage).toBeNull()
    }
  })

  it('defaults metadata to empty object when omitted', () => {
    const { metadata: _meta, ...withoutMeta } = validDeal
    const result = BaseDealSchema.safeParse(withoutMeta)
    expect(result.success).toBe(true)
    if (result.success) {
      expect(result.data.metadata).toEqual({})
    }
  })

  it('supports schema extension for narrowed metadata', () => {
    const DealMetaSchema = z.object({ initialFee: z.number(), monthlyFee: z.number() })
    const NarrowedSchema = BaseDealSchema.extend({ metadata: DealMetaSchema })

    const result = NarrowedSchema.safeParse({
      ...validDeal,
      metadata: { initialFee: 2000, monthlyFee: 500 }
    })
    expect(result.success).toBe(true)
    if (result.success) {
      expect(result.data.metadata.initialFee).toBe(2000)
    }
  })
})

// ---------------------------------------------------------------------------
// BaseCompanySchema
// ---------------------------------------------------------------------------

describe('BaseCompanySchema', () => {
  const validCompany = {
    id: '00000000-0000-0000-0000-000000000050',
    organizationId: ORG_ID,
    name: 'Acme Corp',
    domain: 'acme.com',
    status: 'active',
    metadata: {},
    ...TIMESTAMPS
  }

  it('parses a valid company', () => {
    const result = BaseCompanySchema.safeParse(validCompany)
    expect(result.success).toBe(true)
    if (result.success) {
      expect(result.data.name).toBe('Acme Corp')
      expect(result.data.domain).toBe('acme.com')
    }
  })

  it('accepts null domain', () => {
    const result = BaseCompanySchema.safeParse({ ...validCompany, domain: null })
    expect(result.success).toBe(true)
  })

  it('supports schema extension for narrowed metadata', () => {
    const CompanyMetaSchema = z.object({ reviewCount: z.number().int(), rating: z.number() })
    const NarrowedSchema = BaseCompanySchema.extend({ metadata: CompanyMetaSchema })

    const result = NarrowedSchema.safeParse({
      ...validCompany,
      metadata: { reviewCount: 120, rating: 4.7 }
    })
    expect(result.success).toBe(true)
    if (result.success) {
      expect(result.data.metadata.reviewCount).toBe(120)
      expect(result.data.metadata.rating).toBe(4.7)
    }
  })
})

// ---------------------------------------------------------------------------
// BaseContactSchema
// ---------------------------------------------------------------------------

describe('BaseContactSchema', () => {
  const validContact = {
    id: '00000000-0000-0000-0000-000000000060',
    organizationId: ORG_ID,
    email: 'jane@example.com',
    firstName: 'Jane',
    lastName: 'Doe',
    status: 'active',
    metadata: {},
    ...TIMESTAMPS
  }

  it('parses a valid contact', () => {
    const result = BaseContactSchema.safeParse(validContact)
    expect(result.success).toBe(true)
    if (result.success) {
      expect(result.data.email).toBe('jane@example.com')
      expect(result.data.firstName).toBe('Jane')
    }
  })

  it('accepts null firstName and lastName', () => {
    const result = BaseContactSchema.safeParse({ ...validContact, firstName: null, lastName: null })
    expect(result.success).toBe(true)
  })

  it('supports schema extension for narrowed metadata', () => {
    const ContactMetaSchema = z.object({ linkedinScore: z.number().optional(), tags: z.array(z.string()) })
    const NarrowedSchema = BaseContactSchema.extend({ metadata: ContactMetaSchema })

    const result = NarrowedSchema.safeParse({
      ...validContact,
      metadata: { tags: ['enterprise', 'warm-lead'], linkedinScore: 8 }
    })
    expect(result.success).toBe(true)
    if (result.success) {
      expect(result.data.metadata.tags).toContain('enterprise')
      expect(result.data.metadata.linkedinScore).toBe(8)
    }
  })
})

// ---------------------------------------------------------------------------
// TypeScript generic interface narrowing (compile-time — exercised at runtime)
// ---------------------------------------------------------------------------

describe('BaseProject<TMeta> generic interface', () => {
  it('accepts unnarrowed metadata as Record<string, unknown>', () => {
    const project: BaseProject = {
      id: '00000000-0000-0000-0000-000000000010',
      organizationId: ORG_ID,
      name: 'Generic Project',
      kind: 'internal',
      status: 'active',
      description: null,
      metadata: { anything: true },
      ...TIMESTAMPS
    }
    expect(project.metadata['anything']).toBe(true)
  })

  it('accepts narrowed metadata type', () => {
    type ProjectMeta = { budget: number; riskScore: 'low' | 'medium' | 'high' }
    const project: BaseProject<ProjectMeta> = {
      id: '00000000-0000-0000-0000-000000000011',
      organizationId: ORG_ID,
      name: 'Budget Project',
      kind: 'client_engagement',
      status: 'on_track',
      description: 'A budgeted project',
      metadata: { budget: 100000, riskScore: 'low' },
      ...TIMESTAMPS
    }
    expect(project.metadata.budget).toBe(100000)
    expect(project.metadata.riskScore).toBe('low')
  })
})

describe('BaseDeal<TMeta> generic interface', () => {
  it('accepts unnarrowed metadata', () => {
    const deal: BaseDeal = {
      id: '00000000-0000-0000-0000-000000000040',
      organizationId: ORG_ID,
      contactEmail: 'test@example.com',
      stage: 'interested',
      metadata: {},
      ...TIMESTAMPS
    }
    expect(deal.stage).toBe('interested')
  })

  it('accepts narrowed metadata with deal financials', () => {
    type DealMeta = { contractValue: number; currency: string }
    const deal: BaseDeal<DealMeta> = {
      id: '00000000-0000-0000-0000-000000000041',
      organizationId: ORG_ID,
      contactEmail: 'client@bigco.com',
      stage: 'closing',
      metadata: { contractValue: 24000, currency: 'USD' },
      ...TIMESTAMPS
    }
    expect(deal.metadata.contractValue).toBe(24000)
  })
})

describe('BaseMilestone<TMeta> generic interface', () => {
  it('accepts narrowed metadata', () => {
    type MilestoneMeta = { deliverables: string[] }
    const milestone: BaseMilestone<MilestoneMeta> = {
      id: '00000000-0000-0000-0000-000000000020',
      organizationId: ORG_ID,
      projectId: '00000000-0000-0000-0000-000000000010',
      name: 'Launch',
      status: 'in_progress',
      description: null,
      metadata: { deliverables: ['landing-page', 'email-campaign'] },
      ...TIMESTAMPS
    }
    expect(milestone.metadata.deliverables).toHaveLength(2)
  })
})

describe('BaseTask<TMeta> generic interface', () => {
  it('accepts narrowed metadata', () => {
    type TaskMeta = { storyPoints: number }
    const task: BaseTask<TaskMeta> = {
      id: '00000000-0000-0000-0000-000000000030',
      organizationId: ORG_ID,
      projectId: '00000000-0000-0000-0000-000000000010',
      name: 'Deploy to prod',
      status: 'in_progress',
      type: 'code',
      description: null,
      metadata: { storyPoints: 5 },
      ...TIMESTAMPS
    }
    expect(task.metadata.storyPoints).toBe(5)
  })
})

describe('BaseCompany<TMeta> generic interface', () => {
  it('accepts narrowed metadata', () => {
    type CompanyMeta = { verticalScore: number }
    const company: BaseCompany<CompanyMeta> = {
      id: '00000000-0000-0000-0000-000000000050',
      organizationId: ORG_ID,
      name: 'Tech Startup',
      domain: 'techstartup.io',
      status: 'active',
      metadata: { verticalScore: 92 },
      ...TIMESTAMPS
    }
    expect(company.metadata.verticalScore).toBe(92)
  })
})

describe('BaseContact<TMeta> generic interface', () => {
  it('accepts narrowed metadata', () => {
    type ContactMeta = { personalizationScore: number; openingLine: string }
    const contact: BaseContact<ContactMeta> = {
      id: '00000000-0000-0000-0000-000000000060',
      organizationId: ORG_ID,
      email: 'ceo@example.com',
      firstName: 'Alex',
      lastName: 'Smith',
      status: 'active',
      metadata: { personalizationScore: 87, openingLine: 'Loved your post on AI automation.' },
      ...TIMESTAMPS
    }
    expect(contact.metadata.personalizationScore).toBe(87)
  })
})
