import { z } from 'zod'
import { DescriptionSchema, LabelSchema, ModelIdSchema } from './shared'
import { EventIdSchema } from './resources'
import { defineDomainRecord } from '../helpers'

export const PolicyIdSchema = ModelIdSchema

export const PolicyApplicabilitySchema = z.object({
  systemIds: z.array(ModelIdSchema.meta({ ref: 'system' })).default([]),
  actionIds: z.array(ModelIdSchema.meta({ ref: 'action' })).default([]),
  resourceIds: z.array(ModelIdSchema.meta({ ref: 'resource' })).default([]),
  roleIds: z.array(ModelIdSchema.meta({ ref: 'role' })).default([])
})

export const PolicyTriggerSchema = z.discriminatedUnion('kind', [
  z.object({
    kind: z.literal('event'),
    eventId: EventIdSchema.meta({ ref: 'event' })
  }),
  z.object({
    kind: z.literal('action-invocation'),
    actionId: ModelIdSchema.meta({ ref: 'action' })
  }),
  z.object({
    kind: z.literal('schedule'),
    cron: z.string().trim().min(1).max(120)
  }),
  z.object({
    kind: z.literal('manual')
  })
])

export const PolicyPredicateSchema = z.discriminatedUnion('kind', [
  z.object({
    kind: z.literal('always')
  }),
  z.object({
    kind: z.literal('expression'),
    expression: z.string().trim().min(1).max(2000)
  }),
  z.object({
    kind: z.literal('threshold'),
    metric: ModelIdSchema,
    operator: z.enum(['lt', 'lte', 'eq', 'gte', 'gt']).meta({ label: 'Operator' }),
    value: z.number()
  })
])

export const PolicyEffectSchema = z.discriminatedUnion('kind', [
  z.object({
    kind: z.literal('require-approval'),
    roleId: ModelIdSchema.meta({ ref: 'role' }).optional()
  }),
  z.object({
    kind: z.literal('invoke-action'),
    actionId: ModelIdSchema.meta({ ref: 'action' })
  }),
  z.object({
    kind: z.literal('notify-role'),
    roleId: ModelIdSchema.meta({ ref: 'role' })
  }),
  z.object({
    kind: z.literal('block')
  })
])

export const PolicySchema = z.object({
  id: PolicyIdSchema,
  /** Domain-map iteration order. Convention: multiples of 10 (10, 20, 30, ...) to allow easy insertion. */
  order: z.number(),
  label: LabelSchema,
  description: DescriptionSchema.optional(),
  trigger: PolicyTriggerSchema,
  predicate: PolicyPredicateSchema.default({ kind: 'always' }),
  actions: z.array(PolicyEffectSchema).min(1),
  appliesTo: PolicyApplicabilitySchema.default({
    systemIds: [],
    actionIds: [],
    resourceIds: [],
    roleIds: []
  }),
  lifecycle: z
    .enum(['draft', 'beta', 'active', 'deprecated', 'archived'])
    .meta({ label: 'Lifecycle', color: 'teal' })
    .default('active')
})

export const PoliciesDomainSchema = z
  .record(z.string(), PolicySchema)
  .refine((record) => Object.entries(record).every(([key, entry]) => entry.id === key), {
    message: 'Each policy entry id must match its map key'
  })
  .default({})

export const DEFAULT_ORGANIZATION_MODEL_POLICIES: z.infer<typeof PoliciesDomainSchema> = {}

/** Validate and return a single policy entry. */
export function definePolicy(entry: z.input<typeof PolicySchema>): z.infer<typeof PolicySchema> {
  return PolicySchema.parse(entry)
}

/** Validate and return an id-keyed map of policy entries. */
export function definePolicies(
  entries: readonly z.input<typeof PolicySchema>[]
): Record<string, z.infer<typeof PolicySchema>> {
  return defineDomainRecord(PolicySchema, entries)
}

export type PolicyId = z.infer<typeof PolicyIdSchema>
export type PolicyApplicability = z.infer<typeof PolicyApplicabilitySchema>
export type PolicyTrigger = z.infer<typeof PolicyTriggerSchema>
export type PolicyPredicate = z.infer<typeof PolicyPredicateSchema>
export type PolicyEffect = z.infer<typeof PolicyEffectSchema>
export type Policy = z.infer<typeof PolicySchema>
export type PoliciesDomain = z.infer<typeof PoliciesDomainSchema>
