import { randomUUID } from 'crypto';
import { auditLogger } from '../audit/index.js';

export interface ElevationStore {
  get(key: string): string | undefined;
  set(key: string, value: string): void;
  delete(key: string): void;
}

export interface ElevationAuditContext {
  sessionId: string;
  channel: string;
  sender: string;
}

export interface ElevationWindow {
  untilMs: number;
  id: string;
  reason?: string;
}

export interface ElevationWindowResult {
  window?: ElevationWindow;
  expired: boolean;
}

export interface ElevationStatusOptions {
  auditContext?: ElevationAuditContext;
  nowMs?: number;
  showExpiredSuffix?: boolean;
  reasonSeparator?: string;
}

export interface SetElevationOptions {
  auditContext?: ElevationAuditContext;
  nowMs?: number;
  idGenerator?: () => string;
}

const ELEVATION_UNTIL_KEY = 'elevation.until_ms';
const ELEVATION_REASON_KEY = 'elevation.reason';
const ELEVATION_ID_KEY = 'elevation.id';

export const ELEVATION_USAGE_TEXT = 'Usage: /elevate <duration> <reason...> --yes | /elevate off --yes';
export const ELEVATION_DISABLE_CONFIRM_TEXT =
  'Refusing to disable elevation without explicit confirmation. Use: /elevate off --yes';
export const ELEVATION_ENABLE_CONFIRM_TEXT =
  'Refusing to enable elevation without explicit confirmation. Use: /elevate <duration> <reason...> --yes';
export const ELEVATION_INVALID_DURATION_TEXT = 'Invalid duration. Use one of: 30s, 10m, 1h, 1d';

function clearElevation(store: ElevationStore): void {
  store.delete(ELEVATION_UNTIL_KEY);
  store.delete(ELEVATION_REASON_KEY);
  store.delete(ELEVATION_ID_KEY);
}

function emitExpiredAudit(context: ElevationAuditContext | undefined, id: string, untilMs: number, reason?: string): void {
  if (!context) {
    return;
  }
  auditLogger?.securityElevationExpired({
    session_id: context.sessionId,
    channel: context.channel,
    sender: context.sender,
    elevation_id: id,
    until_ms: untilMs,
    reason: reason || undefined,
  });
}

export function parseElevationDurationToMs(value: string): number | null {
  const m = value.match(/^(\d+)([smhd])$/i);
  if (!m) {
    return null;
  }
  const n = Number.parseInt(m[1], 10);
  if (!Number.isFinite(n) || n <= 0) {
    return null;
  }
  const unit = m[2].toLowerCase();
  if (unit === 's') {return n * 1000;}
  if (unit === 'm') {return n * 60_000;}
  if (unit === 'h') {return n * 3_600_000;}
  if (unit === 'd') {return n * 86_400_000;}
  return null;
}

export function getElevationWindow(
  store: ElevationStore,
  options: {
    nowMs?: number;
    auditContext?: ElevationAuditContext;
  } = {},
): ElevationWindowResult {
  const untilRaw = store.get(ELEVATION_UNTIL_KEY);
  const idRaw = store.get(ELEVATION_ID_KEY);
  const reasonRaw = store.get(ELEVATION_REASON_KEY);
  const nowMs = options.nowMs ?? Date.now();

  if (!untilRaw || !idRaw) {
    return { expired: false };
  }

  const untilMs = Number.parseInt(untilRaw, 10);
  if (!Number.isFinite(untilMs)) {
    clearElevation(store);
    return { expired: false };
  }

  if (untilMs <= nowMs) {
    clearElevation(store);
    emitExpiredAudit(options.auditContext, idRaw, untilMs, reasonRaw ?? undefined);
    return { expired: true };
  }

  return {
    expired: false,
    window: {
      untilMs,
      id: idRaw,
      reason: reasonRaw ?? undefined,
    },
  };
}

export function getElevationStatusMessage(store: ElevationStore, options: ElevationStatusOptions = {}): string {
  const { showExpiredSuffix = false, reasonSeparator = ' — ' } = options;
  const result = getElevationWindow(store, {
    nowMs: options.nowMs,
    auditContext: options.auditContext,
  });
  if (!result.window) {
    if (result.expired && showExpiredSuffix) {
      return 'Elevated mode: off (expired)';
    }
    return 'Elevated mode: off';
  }

  const remainingSec = Math.ceil((result.window.untilMs - (options.nowMs ?? Date.now())) / 1000);
  return `Elevated mode: on (${remainingSec}s remaining)${result.window.reason ? `${reasonSeparator}${result.window.reason}` : ''}`;
}

export function setElevationFromInput(store: ElevationStore, input: string, options: SetElevationOptions = {}): string {
  const raw = input.trim();
  if (!raw) {
    return ELEVATION_USAGE_TEXT;
  }
  const parts = raw.split(/\s+/);
  const hasYes = parts.includes('--yes') || parts.includes('--confirm');
  const filtered = parts.filter((p) => p !== '--yes' && p !== '--confirm');

  if (filtered.length === 0) {
    return ELEVATION_USAGE_TEXT;
  }

  if (filtered[0] === 'off') {
    if (!hasYes) {
      return ELEVATION_DISABLE_CONFIRM_TEXT;
    }
    const existingId = store.get(ELEVATION_ID_KEY) ?? (options.idGenerator ?? randomUUID)();
    const existingUntilRaw = store.get(ELEVATION_UNTIL_KEY);
    const existingReason = store.get(ELEVATION_REASON_KEY) ?? undefined;
    clearElevation(store);
    if (options.auditContext) {
      const untilMs = existingUntilRaw ? Number.parseInt(existingUntilRaw, 10) : undefined;
      auditLogger?.securityElevationDisabled({
        session_id: options.auditContext.sessionId,
        channel: options.auditContext.channel,
        sender: options.auditContext.sender,
        elevation_id: existingId,
        until_ms: Number.isFinite(untilMs) ? untilMs : undefined,
        reason: existingReason || undefined,
      });
    }
    return 'Elevated mode: off';
  }

  if (!hasYes) {
    return ELEVATION_ENABLE_CONFIRM_TEXT;
  }

  const ttlMs = parseElevationDurationToMs(filtered[0]);
  if (!ttlMs) {
    return ELEVATION_INVALID_DURATION_TEXT;
  }

  const nowMs = options.nowMs ?? Date.now();
  const untilMs = nowMs + ttlMs;
  const reason = filtered.slice(1).join(' ').trim();
  const id = (options.idGenerator ?? randomUUID)();

  store.set(ELEVATION_UNTIL_KEY, String(untilMs));
  store.set(ELEVATION_ID_KEY, id);
  if (reason) {
    store.set(ELEVATION_REASON_KEY, reason);
  } else {
    store.delete(ELEVATION_REASON_KEY);
  }

  if (options.auditContext) {
    auditLogger?.securityElevationEnabled({
      session_id: options.auditContext.sessionId,
      channel: options.auditContext.channel,
      sender: options.auditContext.sender,
      elevation_id: id,
      until_ms: untilMs,
      ttl_ms: ttlMs,
      reason: reason || undefined,
    });
  }

  return `Elevated mode: on until ${new Date(untilMs).toISOString()}`;
}
