/**
 * Mosaic Native Kanban — frozen recovery-posture contract v1.
 * Recovery posture is configurable; SOT, write-health, Coordinator authority,
 * and gate semantics are not fields and cannot be overridden.
 */

export const RECOVERY_POSTURE_CONTRACT_VERSION = '1.0.0' as const;
export const recoveryTiers = ['lite', 'standard', 'high-assurance'] as const;
export type RecoveryTier = (typeof recoveryTiers)[number];

export interface OffClusterStorageV1 {
  required: true;
  encrypted: true;
  separateFailureDomain: true;
  minimumCopies: number;
  storageClass: 'encrypted-object-storage' | 'encrypted-backup-target';
}

export interface RecoveryPostureV1 {
  contractVersion: typeof RECOVERY_POSTURE_CONTRACT_VERSION;
  tier: RecoveryTier;
  targetRpoMinutes: number;
  targetRtoMinutes: number;
  baseBackupIntervalHours: number;
  /** null means WAL archival/PITR is disabled. */
  walArchiveIntervalMinutes: number | null;
  /** 0 means PITR is disabled. */
  pitrRetentionDays: number;
  restoreTestIntervalDays: number;
  breakGlassDrillIntervalDays: number;
  offClusterStorage: OffClusterStorageV1;
}

export const recoveryPostureDefaults: Readonly<Record<RecoveryTier, RecoveryPostureV1>> = {
  lite: {
    contractVersion: RECOVERY_POSTURE_CONTRACT_VERSION,
    tier: 'lite',
    targetRpoMinutes: 24 * 60,
    targetRtoMinutes: 24 * 60,
    baseBackupIntervalHours: 24,
    walArchiveIntervalMinutes: null,
    pitrRetentionDays: 0,
    restoreTestIntervalDays: 90,
    breakGlassDrillIntervalDays: 365,
    offClusterStorage: {
      required: true,
      encrypted: true,
      separateFailureDomain: true,
      minimumCopies: 1,
      storageClass: 'encrypted-backup-target',
    },
  },
  standard: {
    contractVersion: RECOVERY_POSTURE_CONTRACT_VERSION,
    tier: 'standard',
    targetRpoMinutes: 60,
    targetRtoMinutes: 8 * 60,
    baseBackupIntervalHours: 24,
    walArchiveIntervalMinutes: 15,
    pitrRetentionDays: 14,
    restoreTestIntervalDays: 90,
    breakGlassDrillIntervalDays: 180,
    offClusterStorage: {
      required: true,
      encrypted: true,
      separateFailureDomain: true,
      minimumCopies: 1,
      storageClass: 'encrypted-object-storage',
    },
  },
  'high-assurance': {
    contractVersion: RECOVERY_POSTURE_CONTRACT_VERSION,
    tier: 'high-assurance',
    targetRpoMinutes: 15,
    targetRtoMinutes: 4 * 60,
    baseBackupIntervalHours: 24,
    walArchiveIntervalMinutes: 5,
    pitrRetentionDays: 35,
    restoreTestIntervalDays: 30,
    breakGlassDrillIntervalDays: 90,
    offClusterStorage: {
      required: true,
      encrypted: true,
      separateFailureDomain: true,
      minimumCopies: 1,
      storageClass: 'encrypted-object-storage',
    },
  },
};

/** Shape schema. Normative cross-field semantics are enforced by validateRecoveryPostureV1. */
export const recoveryPostureJsonSchemaV1 = {
  $id: 'https://mosaicstack.dev/contracts/recovery-posture.v1.schema.json',
  $schema: 'https://json-schema.org/draft/2020-12/schema',
  type: 'object',
  additionalProperties: false,
  required: [
    'contractVersion',
    'tier',
    'targetRpoMinutes',
    'targetRtoMinutes',
    'baseBackupIntervalHours',
    'walArchiveIntervalMinutes',
    'pitrRetentionDays',
    'restoreTestIntervalDays',
    'breakGlassDrillIntervalDays',
    'offClusterStorage',
  ],
  properties: {
    contractVersion: { const: RECOVERY_POSTURE_CONTRACT_VERSION },
    tier: { enum: recoveryTiers },
    targetRpoMinutes: { type: 'integer', minimum: 1 },
    targetRtoMinutes: { type: 'integer', minimum: 1 },
    baseBackupIntervalHours: { type: 'integer', minimum: 1 },
    walArchiveIntervalMinutes: {
      anyOf: [{ type: 'integer', minimum: 1 }, { type: 'null' }],
    },
    pitrRetentionDays: { type: 'integer', minimum: 0 },
    restoreTestIntervalDays: { type: 'integer', minimum: 1 },
    breakGlassDrillIntervalDays: { type: 'integer', minimum: 1 },
    offClusterStorage: {
      type: 'object',
      additionalProperties: false,
      required: ['required', 'encrypted', 'separateFailureDomain', 'minimumCopies', 'storageClass'],
      properties: {
        required: { const: true },
        encrypted: { const: true },
        separateFailureDomain: { const: true },
        minimumCopies: { type: 'integer', minimum: 1 },
        storageClass: {
          enum: ['encrypted-object-storage', 'encrypted-backup-target'],
        },
      },
    },
  },
} as const;

export const recoveryValidationCodes = [
  'INVALID_SHAPE',
  'UNKNOWN_FIELD',
  'PITR_REQUIRES_WAL',
  'WAL_REQUIRES_PITR',
  'RPO_BETTER_THAN_MECHANISM',
  'OFF_CLUSTER_REQUIRED',
  'HIGH_ASSURANCE_WEAKENED',
] as const;
export type RecoveryValidationCode = (typeof recoveryValidationCodes)[number];

export interface RecoveryValidationIssueV1 {
  code: RecoveryValidationCode;
  path: string;
  message: string;
}
export type RecoveryValidationResultV1 =
  | { ok: true; value: RecoveryPostureV1 }
  | { ok: false; issues: RecoveryValidationIssueV1[] };

const topLevelFields = new Set([
  'contractVersion',
  'tier',
  'targetRpoMinutes',
  'targetRtoMinutes',
  'baseBackupIntervalHours',
  'walArchiveIntervalMinutes',
  'pitrRetentionDays',
  'restoreTestIntervalDays',
  'breakGlassDrillIntervalDays',
  'offClusterStorage',
]);
const storageFields = new Set([
  'required',
  'encrypted',
  'separateFailureDomain',
  'minimumCopies',
  'storageClass',
]);

function isRecord(value: unknown): value is Record<string, unknown> {
  return typeof value === 'object' && value !== null && !Array.isArray(value);
}
function isPositiveInteger(value: unknown): value is number {
  return Number.isInteger(value) && Number(value) > 0;
}
function isNonnegativeInteger(value: unknown): value is number {
  return Number.isInteger(value) && Number(value) >= 0;
}

/**
 * Normative parser/refinement. Deployment code MUST call this function (or a
 * byte-for-byte behaviorally equivalent generated validator), not JSON Schema
 * shape validation alone.
 */
export function validateRecoveryPostureV1(input: unknown): RecoveryValidationResultV1 {
  const issues: RecoveryValidationIssueV1[] = [];
  if (!isRecord(input)) {
    return {
      ok: false,
      issues: [{ code: 'INVALID_SHAPE', path: '$', message: 'posture must be an object' }],
    };
  }

  for (const key of Object.keys(input)) {
    if (!topLevelFields.has(key)) {
      issues.push({ code: 'UNKNOWN_FIELD', path: `$.${key}`, message: 'unknown field' });
    }
  }

  const tier = input['tier'];
  const storage = input['offClusterStorage'];
  const integerFields = [
    'targetRpoMinutes',
    'targetRtoMinutes',
    'baseBackupIntervalHours',
    'restoreTestIntervalDays',
    'breakGlassDrillIntervalDays',
  ] as const;

  if (input['contractVersion'] !== RECOVERY_POSTURE_CONTRACT_VERSION) {
    issues.push({
      code: 'INVALID_SHAPE',
      path: '$.contractVersion',
      message: `must equal ${RECOVERY_POSTURE_CONTRACT_VERSION}`,
    });
  }
  if (!recoveryTiers.includes(tier as RecoveryTier)) {
    issues.push({ code: 'INVALID_SHAPE', path: '$.tier', message: 'unknown recovery tier' });
  }
  for (const field of integerFields) {
    if (!isPositiveInteger(input[field])) {
      issues.push({
        code: 'INVALID_SHAPE',
        path: `$.${field}`,
        message: 'must be a positive integer',
      });
    }
  }
  if (!isNonnegativeInteger(input['pitrRetentionDays'])) {
    issues.push({
      code: 'INVALID_SHAPE',
      path: '$.pitrRetentionDays',
      message: 'must be a nonnegative integer',
    });
  }
  if (
    input['walArchiveIntervalMinutes'] !== null &&
    !isPositiveInteger(input['walArchiveIntervalMinutes'])
  ) {
    issues.push({
      code: 'INVALID_SHAPE',
      path: '$.walArchiveIntervalMinutes',
      message: 'must be null or a positive integer',
    });
  }

  if (!isRecord(storage)) {
    issues.push({
      code: 'INVALID_SHAPE',
      path: '$.offClusterStorage',
      message: 'must be an object',
    });
  } else {
    for (const key of Object.keys(storage)) {
      if (!storageFields.has(key)) {
        issues.push({
          code: 'UNKNOWN_FIELD',
          path: `$.offClusterStorage.${key}`,
          message: 'unknown field',
        });
      }
    }
    if (
      storage['required'] !== true ||
      storage['encrypted'] !== true ||
      storage['separateFailureDomain'] !== true
    ) {
      issues.push({
        code: 'OFF_CLUSTER_REQUIRED',
        path: '$.offClusterStorage',
        message: 'storage must be required, encrypted, and in a separate failure domain',
      });
    }
    if (!isPositiveInteger(storage['minimumCopies'])) {
      issues.push({
        code: 'INVALID_SHAPE',
        path: '$.offClusterStorage.minimumCopies',
        message: 'must be a positive integer',
      });
    }
    if (
      storage['storageClass'] !== 'encrypted-object-storage' &&
      storage['storageClass'] !== 'encrypted-backup-target'
    ) {
      issues.push({
        code: 'INVALID_SHAPE',
        path: '$.offClusterStorage.storageClass',
        message: 'unsupported storage class',
      });
    }
  }

  const wal = input['walArchiveIntervalMinutes'];
  const pitr = input['pitrRetentionDays'];
  if (pitr !== 0 && wal === null) {
    issues.push({
      code: 'PITR_REQUIRES_WAL',
      path: '$.pitrRetentionDays',
      message: 'PITR retention requires WAL archival',
    });
  }
  if (wal !== null && pitr === 0) {
    issues.push({
      code: 'WAL_REQUIRES_PITR',
      path: '$.walArchiveIntervalMinutes',
      message: 'WAL archival requires positive PITR retention',
    });
  }

  if (
    isPositiveInteger(input['targetRpoMinutes']) &&
    isPositiveInteger(input['baseBackupIntervalHours']) &&
    (wal === null || isPositiveInteger(wal))
  ) {
    const mechanismMinutes = wal === null ? input['baseBackupIntervalHours'] * 60 : wal;
    if (mechanismMinutes > input['targetRpoMinutes']) {
      issues.push({
        code: 'RPO_BETTER_THAN_MECHANISM',
        path: '$.targetRpoMinutes',
        message: `configured mechanism can only support ${mechanismMinutes} minutes`,
      });
    }
  }

  if (tier === 'high-assurance') {
    const weakened =
      !isPositiveInteger(input['targetRpoMinutes']) ||
      input['targetRpoMinutes'] > 15 ||
      !isPositiveInteger(input['targetRtoMinutes']) ||
      input['targetRtoMinutes'] > 4 * 60 ||
      !isPositiveInteger(input['baseBackupIntervalHours']) ||
      input['baseBackupIntervalHours'] > 24 ||
      !isPositiveInteger(wal) ||
      wal > 5 ||
      !isNonnegativeInteger(pitr) ||
      pitr < 35 ||
      !isPositiveInteger(input['restoreTestIntervalDays']) ||
      input['restoreTestIntervalDays'] > 30 ||
      !isPositiveInteger(input['breakGlassDrillIntervalDays']) ||
      input['breakGlassDrillIntervalDays'] > 90;
    if (weakened) {
      issues.push({
        code: 'HIGH_ASSURANCE_WEAKENED',
        path: '$',
        message: 'high-assurance posture may be strengthened but not weakened',
      });
    }
  }

  if (issues.length > 0) return { ok: false, issues };
  return { ok: true, value: input as unknown as RecoveryPostureV1 };
}

export interface RecoveryPostureOverrideAuditV1 {
  actorId: string;
  reason: string;
  effectiveAt: string;
  policyRevision: string;
  previous: RecoveryPostureV1;
  next: RecoveryPostureV1;
}
