import type { ScheduledTask } from '@/types/scheduling';
import type { PermissionAction } from '@/context/AuthContext';

/**
 * Single source of truth for scheduled-operation action metadata on the
 * frontend. Drives the create-flow action picker, the All Tasks label column,
 * the Timeline lanes, and the mobile schedule labels/tones. Adding a new action
 * means adding one entry here (plus its execution logic on the backend).
 *
 * The backend keeps a leaner validation-only registry in
 * `backend/src/services/scheduledActionRegistry.ts`; the packages build in
 * isolation so the two cannot share a module. Tests on each side keep the
 * action sets in lockstep.
 */

/** Backend action ids (the 9 values that travel on the wire). */
export type BackendAction = ScheduledTask['action'];

/**
 * UI action ids. `update-fleet` is a frontend-only alias for `update` with
 * `target_type: 'fleet'`; it never reaches the backend.
 */
export type ScheduledActionId = BackendAction | 'update-fleet' | 'update-by-label' | 'container-restart' | 'container-stop' | 'container-start';

export type ScheduledActionCategory = 'lifecycle' | 'updates' | 'security' | 'maintenance' | 'backups';
export type ScheduledActionTone = 'success' | 'warning' | 'destructive' | 'brand';

/** Risk level assigned to each scheduled action, shown as a badge in the create/edit form. */
export type ScheduledActionRiskLevel = 'safe' | 'read-only' | 'interruptive' | 'runtime-change' | 'removes-containers' | 'destructive';

/** Human-readable label for each risk level. */
export const RISK_LABEL: Record<ScheduledActionRiskLevel, string> = {
  'safe': 'Safe',
  'read-only': 'Read-only',
  'interruptive': 'Interruptive',
  'runtime-change': 'Runtime change',
  'removes-containers': 'Removes containers',
  'destructive': 'Destructive',
};

/** Design-system tone for each risk level. */
export const RISK_TONE: Record<ScheduledActionRiskLevel, ScheduledActionTone> = {
  'safe': 'success',
  'read-only': 'brand',
  'interruptive': 'warning',
  'runtime-change': 'warning',
  'removes-containers': 'destructive',
  'destructive': 'destructive',
};

/** Chip border/background/text classes for each risk level. */
export const RISK_BADGE_CLASSES: Record<ScheduledActionRiskLevel, string> = {
  'safe': 'border-success/25 bg-success/8 text-success',
  'read-only': 'border-brand/25 bg-brand/8 text-brand',
  'interruptive': 'border-warning/25 bg-warning/8 text-warning',
  'runtime-change': 'border-warning/25 bg-warning/8 text-warning',
  'removes-containers': 'border-destructive/25 bg-destructive/8 text-destructive',
  'destructive': 'border-destructive/25 bg-destructive/8 text-destructive',
};

/** Leading dot fill class for each risk level. */
export const RISK_DOT_CLASSES: Record<ScheduledActionRiskLevel, string> = {
  'safe': 'bg-success',
  'read-only': 'bg-brand',
  'interruptive': 'bg-warning',
  'runtime-change': 'bg-warning',
  'removes-containers': 'bg-destructive',
  'destructive': 'bg-destructive',
};

export interface ScheduledActionDefinition {
  id: ScheduledActionId;
  /** The action value sent to the backend (`update-fleet` maps to `update`). */
  backendAction: BackendAction;
  label: string;
  /** Compact label used on the mobile schedule view. */
  shortLabel: string;
  category: ScheduledActionCategory;
  targetType: ScheduledTask['target_type'];
  tone: ScheduledActionTone;
  requiresNode: boolean;
  requiresStack: boolean;
  requiresContainer: boolean;
  supportsServiceSelection: boolean;
  nodeScope?: 'local';
  /** One-line explanation shown below the action picker in the create/edit form. */
  helperText: string;
  /** Risk level shown as a coloured chip next to the helper text. */
  riskLevel: ScheduledActionRiskLevel;
  /** Permission required to schedule this action (mirrors backend registry). */
  permission: PermissionAction;
}

/** Action pre-selected when the create modal opens. Decoupled from picker order. */
export const DEFAULT_SCHEDULED_ACTION_ID: ScheduledActionId = 'restart';

/** Ordered for the create-flow action picker, grouped by category. */
export const SCHEDULED_ACTIONS: ScheduledActionDefinition[] = [
  // Lifecycle
  { id: 'auto_backup', backendAction: 'auto_backup', label: 'Backup Stack Compose Files', shortLabel: 'backup', category: 'lifecycle', targetType: 'stack', tone: 'brand', requiresNode: true, requiresStack: true, requiresContainer: false, supportsServiceSelection: false, helperText: 'Backs up compose and env files only. This does not back up application volumes.', riskLevel: 'safe', permission: 'stack:deploy' },
  { id: 'auto_start', backendAction: 'auto_start', label: 'Start / Bring Up Stack', shortLabel: 'start', category: 'lifecycle', targetType: 'stack', tone: 'success', requiresNode: true, requiresStack: true, requiresContainer: false, supportsServiceSelection: false, helperText: 'Creates containers if they do not exist, or starts existing stopped containers.', riskLevel: 'runtime-change', permission: 'stack:deploy' },
  { id: 'restart', backendAction: 'restart', label: 'Restart Stack', shortLabel: 'restart', category: 'lifecycle', targetType: 'stack', tone: 'brand', requiresNode: true, requiresStack: true, requiresContainer: false, supportsServiceSelection: true, helperText: 'Restarts containers in place. Running services are stopped and started again on the same configuration.', riskLevel: 'interruptive', permission: 'stack:deploy' },
  { id: 'auto_stop', backendAction: 'auto_stop', label: 'Stop Stack', shortLabel: 'stop', category: 'lifecycle', targetType: 'stack', tone: 'warning', requiresNode: true, requiresStack: true, requiresContainer: false, supportsServiceSelection: false, helperText: 'Stops containers but keeps them in place for a faster start later.', riskLevel: 'interruptive', permission: 'stack:deploy' },
  { id: 'auto_down', backendAction: 'auto_down', label: 'Take Stack Down', shortLabel: 'down', category: 'lifecycle', targetType: 'stack', tone: 'destructive', requiresNode: true, requiresStack: true, requiresContainer: false, supportsServiceSelection: false, helperText: 'Runs compose down. Containers are removed, but compose files remain on disk.', riskLevel: 'removes-containers', permission: 'stack:deploy' },
  { id: 'container-restart', backendAction: 'restart', label: 'Restart Container', shortLabel: 'restart ctr', category: 'lifecycle', targetType: 'container', tone: 'brand', requiresNode: true, requiresStack: false, requiresContainer: true, supportsServiceSelection: false, helperText: 'Restarts a single container by name on the selected node. Targets the container directly, not through compose.', riskLevel: 'interruptive', permission: 'node:manage' },
  { id: 'container-stop', backendAction: 'auto_stop', label: 'Stop Container', shortLabel: 'stop ctr', category: 'lifecycle', targetType: 'container', tone: 'warning', requiresNode: true, requiresStack: false, requiresContainer: true, supportsServiceSelection: false, helperText: 'Stops a single container by name. The container remains on disk for a faster start later.', riskLevel: 'interruptive', permission: 'node:manage' },
  { id: 'container-start', backendAction: 'auto_start', label: 'Start Container', shortLabel: 'start ctr', category: 'lifecycle', targetType: 'container', tone: 'success', requiresNode: true, requiresStack: false, requiresContainer: true, supportsServiceSelection: false, helperText: 'Starts a stopped container by name on the selected node.', riskLevel: 'runtime-change', permission: 'node:manage' },
  // Updates
  { id: 'update', backendAction: 'update', label: 'Auto-update Stack', shortLabel: 'update', category: 'updates', targetType: 'stack', tone: 'success', requiresNode: true, requiresStack: true, requiresContainer: false, supportsServiceSelection: false, helperText: 'Checks this stack\'s images and recreates the stack only when newer images are available.', riskLevel: 'runtime-change', permission: 'stack:deploy' },
  { id: 'update-fleet', backendAction: 'update', label: 'Auto-update All Stacks on Node', shortLabel: 'update node', category: 'updates', targetType: 'fleet', tone: 'success', requiresNode: true, requiresStack: false, requiresContainer: false, supportsServiceSelection: false, helperText: 'Checks every stack on the selected node and updates stacks with newer images.', riskLevel: 'runtime-change', permission: 'node:manage' },
  { id: 'update-by-label', backendAction: 'update', label: 'Auto-update stacks by label', shortLabel: 'update label', category: 'updates', targetType: 'fleet', tone: 'success', requiresNode: false, requiresStack: false, requiresContainer: false, supportsServiceSelection: false, helperText: 'Resolves stacks that currently carry a Stack Label at each run, across the entire fleet or one node, and updates those with newer images.', riskLevel: 'runtime-change', permission: 'node:manage' },
  // Security
  { id: 'scan', backendAction: 'scan', label: 'Scan Node Images', shortLabel: 'scan', category: 'security', targetType: 'system', tone: 'success', requiresNode: true, requiresStack: false, requiresContainer: false, supportsServiceSelection: false, nodeScope: 'local', helperText: 'Runs Trivy against images on the selected local node and records the findings.', riskLevel: 'read-only', permission: 'node:manage' },
  // Maintenance
  { id: 'prune', backendAction: 'prune', label: 'Prune Node Resources', shortLabel: 'prune', category: 'maintenance', targetType: 'system', tone: 'warning', requiresNode: true, requiresStack: false, requiresContainer: false, supportsServiceSelection: false, nodeScope: 'local', helperText: 'Removes unused Docker resources on the selected node. Be careful when pruning volumes.', riskLevel: 'destructive', permission: 'system:settings' },
  // Backups
  { id: 'snapshot', backendAction: 'snapshot', label: 'Create Fleet Snapshot', shortLabel: 'snapshot', category: 'backups', targetType: 'fleet', tone: 'warning', requiresNode: false, requiresStack: false, requiresContainer: false, supportsServiceSelection: false, helperText: 'Creates a versioned snapshot of compose and env files across the fleet.', riskLevel: 'safe', permission: 'node:manage' },
];

const ACTION_BY_ID = new Map<string, ScheduledActionDefinition>(SCHEDULED_ACTIONS.map(a => [a.id, a]));

export function getActionById(id: string): ScheduledActionDefinition | undefined {
  return ACTION_BY_ID.get(id);
}

/**
 * Resolve a stored task to its action definition. A stored `update` task with a
 * stack-label selector maps to `update-by-label`; a plain fleet update maps to
 * `update-fleet`; everything else maps by its backend action id.
 */
export function resolveTaskAction(
  task: Pick<ScheduledTask, 'action' | 'target_type'> & { selector_type?: string | null },
): ScheduledActionDefinition | undefined {
  if (task.action === 'update' && task.target_type === 'fleet' && task.selector_type === 'stack-label') {
    return getActionById('update-by-label');
  }
  if (task.action === 'update' && task.target_type === 'fleet') {
    return getActionById('update-fleet');
  }
  if (task.target_type === 'container') {
    if (task.action === 'restart') return getActionById('container-restart');
    if (task.action === 'auto_stop') return getActionById('container-stop');
    if (task.action === 'auto_start') return getActionById('container-start');
  }
  return getActionById(task.action);
}

/** Drop a trailing `.yml` / `.yaml` from a stack file name for display. */
export function stripComposeExt(name: string): string {
  return name.replace(/\.(ya?ml)$/, '');
}

/**
 * Category-aware label for what a scheduled run acts on, used by the Timeline
 * pills and the mobile schedule list. Stack actions show the stack, fleet
 * snapshots show the whole fleet, fleet updates and node-scoped actions
 * (prune / scan) show the selected node when its name is known. Label-targeted
 * updates show the label name and fleet or node scope.
 */
export function scheduleTargetDescriptor(
  task: Pick<ScheduledTask, 'action' | 'target_type' | 'target_id' | 'name'> & {
    selector_type?: string | null;
    selector_value?: string | null;
    node_id?: number | null;
  },
  nodeName?: string,
): string {
  if (task.selector_type === 'stack-label' && task.selector_value) {
    if (task.node_id != null) {
      return `Label: ${task.selector_value} · ${nodeName ?? `node ${task.node_id}`}`;
    }
    return `Label: ${task.selector_value} · Entire fleet`;
  }
  switch (task.target_type) {
    case 'stack':
      return stripComposeExt(task.target_id ?? task.name);
    case 'fleet':
      return task.action === 'update'
        ? (nodeName ? `All stacks · ${nodeName}` : 'All stacks')
        : 'Entire fleet';
    case 'system':
      return nodeName ?? 'Selected node';
    case 'container':
      return task.target_id ?? task.name;
    default: {
      const exhaustive: never = task.target_type;
      return exhaustive;
    }
  }
}

export interface ScheduledActionCategoryLane {
  key: ScheduledActionCategory;
  label: string;
  color: string;
  bg: string;
}

/** Ordered Timeline lanes; each scheduled action maps to one lane by category. */
export const SCHEDULED_ACTION_CATEGORIES: ScheduledActionCategoryLane[] = [
  { key: 'lifecycle', label: 'Lifecycle', color: 'var(--label-blue)', bg: 'var(--label-blue-bg)' },
  { key: 'updates', label: 'Updates', color: 'var(--success)', bg: 'oklch(from var(--success) l c h / 0.18)' },
  { key: 'security', label: 'Security', color: 'var(--label-purple)', bg: 'var(--label-purple-bg)' },
  { key: 'maintenance', label: 'Upkeep', color: 'var(--warning)', bg: 'oklch(from var(--warning) l c h / 0.18)' },
  { key: 'backups', label: 'Backups', color: 'var(--brand)', bg: 'oklch(from var(--brand) l c h / 0.18)' },
];

// ── Permission helpers ──────────────────────────────────────────────────────

/** Permission actions that authorize any scheduleable action. */
const SCHEDULABLE_ACTIONS: readonly PermissionAction[] = ['stack:deploy', 'node:manage', 'system:settings'];

export interface ScheduleActionTarget {
  nodeId?: number | null;
  stackName?: string | null;
  labelScope?: 'fleet' | 'node';
}

/**
 * Check whether the user can schedule the given action on the given target.
 * Scope resolution mirrors the backend `resolveTaskPermissionScope`: per-action,
 * not per target-type bucket.
 */
export function canScheduleAction(
  can: (action: PermissionAction, resourceType?: string, resourceId?: string, nodeId?: number | null) => boolean,
  def: ScheduledActionDefinition,
  target: ScheduleActionTarget,
): boolean {
  // Stack lifecycle: scoped to (nodeId, stackName)
  if (def.targetType === 'stack') {
    return can(def.permission, 'stack', target.stackName ?? undefined, target.nodeId);
  }
  // Prune: always unscoped (admin-only via system:settings in the role matrix)
  if (def.id === 'prune') {
    return can(def.permission);
  }
  // Snapshot: unscoped (spans all nodes)
  if (def.id === 'snapshot') {
    return can(def.permission);
  }
  // Container targets + scan: node-scoped
  if (def.targetType === 'container' || def.id === 'scan') {
    return can(def.permission, 'node', target.nodeId != null ? String(target.nodeId) : undefined, target.nodeId);
  }
  // Fleet update with specific node (non-label): node-scoped
  if (def.id === 'update-fleet') {
    return can(def.permission, 'node', target.nodeId != null ? String(target.nodeId) : undefined, target.nodeId);
  }
  // Fleet-wide label update: unscoped when no node; node-scoped when node
  if (def.id === 'update-by-label') {
    if (target.labelScope === 'node' && target.nodeId != null) {
      return can(def.permission, 'node', String(target.nodeId), target.nodeId);
    }
    return can(def.permission);
  }
  return can(def.permission);
}

/**
 * True when the user can schedule at least one action. Used to determine whether
 * the Scheduled Operations view and its "New scheduled task" button should be
 * reachable.
 */
export function canScheduleAny(
  can: (action: PermissionAction, resourceType?: string, resourceId?: string, nodeId?: number | null) => boolean,
  permissions?: { scopedPermissions?: Record<string, PermissionAction[]> } | null,
): boolean {
  // Global role check
  if (can('stack:deploy') || can('node:manage') || can('system:settings')) return true;
  // Scoped permissions check: any scoped grant covering a scheduleable action
  if (permissions?.scopedPermissions) {
    for (const actions of Object.values(permissions.scopedPermissions)) {
      if (actions.some(a => (SCHEDULABLE_ACTIONS as readonly string[]).includes(a))) return true;
    }
  }
  return false;
}

/**
 * True when the user can schedule this action on at least one possible target
 * (global role or any scoped grant). Used to filter the action picker so
 * actions the user can NEVER schedule are not shown.
 */
export function canScheduleActionAnywhere(
  can: (action: PermissionAction, resourceType?: string, resourceId?: string, nodeId?: number | null) => boolean,
  def: ScheduledActionDefinition,
  permissions?: { scopedPermissions?: Record<string, PermissionAction[]> } | null,
): boolean {
  if (can(def.permission)) return true;
  if (permissions?.scopedPermissions) {
    for (const actions of Object.values(permissions.scopedPermissions)) {
      if ((actions as readonly string[]).includes(def.permission)) return true;
    }
  }
  return false;
}
