import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'
import { HydratedDocument, Types } from 'mongoose'

export type TenantDocument = HydratedDocument<Tenant>

export type TenantStatus = 'pending' | 'active' | 'suspended' | 'deleted'
export type TenantPlan = 'mvp' | 'pro' | 'enterprise'

// One field per external integration. 'pending' = not yet tried; 'ok' = synced;
// 'error' = last attempt failed (see provisioningErrors for detail).
export type IntegrationState = 'pending' | 'ok' | 'error' | 'skipped'

@Schema({ collection: 'tenants', timestamps: true })
export class Tenant {
  // URL-safe identifier, also used as Authentik group name. Lowercase, hyphenated.
  @Prop({ required: true, unique: true, index: true, lowercase: true, trim: true })
  slug!: string

  @Prop({ required: true, trim: true })
  name!: string

  @Prop({ enum: ['pending', 'active', 'suspended', 'deleted'], default: 'pending', index: true })
  status!: TenantStatus

  @Prop({ enum: ['mvp', 'pro', 'enterprise'], default: 'mvp' })
  plan!: TenantPlan

  // Initial seat count from provisioning. Used for portfolio displays and
  // (later) MRR calculations. The "used" count comes from User.tenantIds —
  // not stored here to avoid a denormalized field that drifts on every
  // user-add/remove. Default 0 so older docs without this field render
  // as "0 / N" without throwing.
  @Prop({ type: Number, min: 0, default: 0 })
  seats!: number

  // Custom domains attached to this tenant. First entry is the primary host.
  @Prop({ type: [String], default: [] })
  domains!: string[]

  // Partner-editable customer metadata. Display-only — not used for
  // provisioning. `industry` is free text; `brandColor` is a #rrggbb hex
  // (validated at the DTO layer) rendered as the customer's swatch in the
  // partner portal.
  @Prop({ trim: true })
  industry?: string

  @Prop({ trim: true })
  brandColor?: string

  // Optional MSP/reseller this tenant belongs to. Sparse — direct tenants have none.
  @Prop({ type: Types.ObjectId, ref: 'Partner', index: true, sparse: true })
  partnerId?: Types.ObjectId

  // External system handles — filled in by the provisioning worker (Phase 4)
  @Prop({ index: true, sparse: true })
  authentikGroupId?: string

  @Prop({ sparse: true })
  ocisSpaceId?: string

  @Prop({ sparse: true })
  stalwartDomain?: string

  // Free-form billing context. Stripe IDs live on Subscription, not here.
  @Prop({
    type: {
      companyName: String,
      vatId: String,
      country: String,
      contactEmail: String,
    },
    default: {},
  })
  billingInfo!: {
    companyName?: string
    vatId?: string
    country?: string
    contactEmail?: string
  }

  // Customer-managed security policy. Stored intent; enforcement is wired
  // incrementally (MFA enrollment is read live; MFA/geo/session enforcement
  // via Authentik lands in a later stage). mfaMode: 'all' | 'admins' |
  // 'optional'. Timeouts in minutes/hours. allowedCountries = ISO alpha-2;
  // ipAllowlist = CIDR strings.
  @Prop({
    type: {
      mfaMode: { type: String, enum: ['all', 'admins', 'optional'], default: 'optional' },
      sessionIdleMinutes: { type: Number, min: 0 },
      sessionAbsoluteHours: { type: Number, min: 0 },
      allowedCountries: { type: [String], default: undefined },
      ipAllowlist: { type: [String], default: undefined },
    },
    default: () => ({ mfaMode: 'optional' }),
  })
  securityPolicy!: {
    mfaMode: 'all' | 'admins' | 'optional'
    sessionIdleMinutes?: number
    sessionAbsoluteHours?: number
    allowedCountries?: string[]
    ipAllowlist?: string[]
  }

  // Per-integration provisioning state. Each one is updated independently when its
  // upstream API call succeeds or fails — orchestration is best-effort, not atomic.
  @Prop({
    type: {
      authentik: { type: String, enum: ['pending', 'ok', 'error', 'skipped'], default: 'pending' },
      stalwart: { type: String, enum: ['pending', 'ok', 'error', 'skipped'], default: 'pending' },
      ocis: { type: String, enum: ['pending', 'ok', 'error', 'skipped'], default: 'pending' },
    },
    default: () => ({ authentik: 'pending', stalwart: 'pending', ocis: 'pending' }),
  })
  provisioningStatus!: {
    authentik: IntegrationState
    stalwart: IntegrationState
    ocis: IntegrationState
  }

  // Last error message per integration. Cleared when a subsequent attempt succeeds.
  @Prop({ type: Object, default: {} })
  provisioningErrors!: {
    authentik?: string
    stalwart?: string
    ocis?: string
  }
}

export const TenantSchema = SchemaFactory.createForClass(Tenant)
