import type { RedisInfo } from 'redis-info';
import type { BaseAdapter } from '../baseAdapter';
import type { STATUSES } from '../dist/constants/statuses';

export type JobCleanStatus = 'completed' | 'wait' | 'active' | 'delayed' | 'failed';

export type JobRetryStatus = 'completed' | 'failed';

export type MetricsType = 'completed' | 'failed';

export interface QueueMetrics {
  meta: {
    count: number;
    prevTS: number;
    prevCount: number;
  };
  data: number[];
  count: number;
}

export type MetricsHistoryGranularity = 'hour' | 'day';

export interface MetricsHistoryQuery {
  /** Queue name (namespaced, as returned by adapter.getName()). Omit for the cross-queue global rollup. */
  queue?: string;
  metric: MetricsType;
  /** Inclusive lower bound, epoch ms. */
  from: number;
  /** Inclusive upper bound, epoch ms. */
  to: number;
  granularity: MetricsHistoryGranularity;
}

export interface MetricsHistoryPoint {
  /** Bucket start, epoch ms (UTC-aligned to the granularity). */
  ts: number;
  value: number;
}

export interface MetricsHistoryTierUsage {
  keys: number;
  bytes: number;
}

export interface MetricsHistoryQueueUsage {
  queue: string;
  keys: number;
  bytes: number;
  minutes: number;
  days: string[];
  tiers: Record<'minute' | 'hour' | 'day', MetricsHistoryTierUsage>;
}

export interface MetricsHistoryUsage {
  keys: number;
  bytes: number;
  minutes: number;
  oldestDay: string | null;
  newestDay: string | null;
  tiers: Record<'minute' | 'hour' | 'day', MetricsHistoryTierUsage>;
  queues: MetricsHistoryQueueUsage[];
}

export interface MetricsHistoryPurgeOptions {
  queue?: string;
  /** ISO `YYYY-MM-DD`. Drops days strictly before it; omit to drop everything in scope. */
  before?: string;
}

export interface MetricsHistoryPurgeResult {
  keysDeleted: number;
  fieldsDeleted: number;
}

/**
 * Seam the core uses to serve long-retention metrics history.
 * The concrete implementation lives in the opt-in @bull-board/metrics package.
 * The core never stores anything; it only calls this interface.
 *
 * `getUsage` and `purge` are optional. Their routes are registered only when a provider
 * implements them, so a custom read-only provider stays valid and the UI never offers a
 * storage panel that has nothing behind it.
 */
export interface MetricsHistoryProvider {
  getHistory(query: MetricsHistoryQuery): Promise<MetricsHistoryPoint[]>;
  getUsage?(): Promise<MetricsHistoryUsage>;
  purge?(options: MetricsHistoryPurgeOptions): Promise<MetricsHistoryPurgeResult>;
}

type Library = 'bull' | 'bullmq';

type BullMQStatuses = STATUSES;
type BullStatuses = Exclude<BullMQStatuses, 'prioritized' | 'waiting-children'>;

export type Status<Lib extends Library = 'bullmq'> = Lib extends 'bullmq'
  ? BullMQStatuses
  : Lib extends 'bull'
    ? BullStatuses
    : never;

export type JobStatus<Lib extends Library = 'bullmq'> = Lib extends 'bullmq'
  ? Exclude<BullMQStatuses, 'latest'>
  : Lib extends 'bull'
    ? Exclude<BullStatuses, 'latest'>
    : never;

export type JobCounts = Record<Status, number>;
export type ExternalJobUrl = {
  displayText?: string;
  href: string;
};

export interface QueueAdapterOptions {
  readOnlyMode: boolean;
  allowRetries: boolean;
  prefix: string;
  description: string;
  displayName: string;
  delimiter: string;
  externalJobUrl?: (job: QueueJobJson) => ExternalJobUrl;
  jobDataSchema?: Record<string, any>;
}

export type BullBoardQueues = Map<string, BaseAdapter>;

export interface QueueJob {
  repeatJobKey?: string;

  opts: {
    delay?: number | undefined;
  };

  promote(): Promise<void>;

  remove(): Promise<void>;

  retry(state?: JobRetryStatus): Promise<void>;

  toJSON(): QueueJobJson;

  getState(): Promise<Status | 'stuck' | 'waiting-children' | 'prioritized' | 'unknown'>;

  update?(jobData: Record<string, any>): Promise<void>;

  updateData?(jobData: Record<string, any>): Promise<void>;
}

export interface QueueJobJson {
  // add properties as needed from real Bull/BullMQ jobs
  id?: string | undefined | number | null;
  name: string;
  progress: string | boolean | number | object;
  attemptsMade: number;
  finishedOn?: number | null;
  processedOn?: number | null;
  processedBy?: string | null;
  delay?: number;
  timestamp: number;
  failedReason: string;
  stacktrace: string[] | null;
  data: any;
  returnvalue: any;
  opts: any;
  parentKey?: string;
  repeatJobKey?: string;
}

export interface QueueJobOptions {
  delay?: number;
  attempts?: number;
}

export type JobRetentionOption = boolean | number | { age?: number; count?: number };

export interface QueueDefaultJobOptions {
  attempts?: number;
  delay?: number;
  priority?: number;
  lifo?: boolean;
  backoff?: number | { type: string; delay?: number };
  removeOnComplete?: JobRetentionOption;
  removeOnFail?: JobRetentionOption;
  [option: string]: unknown;
}

export interface RedisStats {
  version: string;
  mode: RedisInfo['redis_mode'];
  port: number;
  os: string;
  uptime: number;
  memory: {
    total: number;
    used: number;
    fragmentationRatio: number;
    peak: number;
  };
  clients: {
    connected: number;
    blocked: number;
  };
}

export interface AppJob {
  id: QueueJobJson['id'];
  name: QueueJobJson['name'];
  timestamp: QueueJobJson['timestamp'];
  processedOn?: QueueJobJson['processedOn'];
  processedBy?: QueueJobJson['processedBy'];
  finishedOn?: QueueJobJson['finishedOn'];
  progress: QueueJobJson['progress'];
  attempts: QueueJobJson['attemptsMade'];
  failedReason: QueueJobJson['failedReason'];
  stacktrace: string[];
  delay: number | undefined;
  opts: QueueJobJson['opts'];
  data: QueueJobJson['data'];
  returnValue: QueueJobJson['returnvalue'];
  isFailed: boolean;
  externalUrl?: {
    displayText?: string;
    href: string;
  };
  groupId?: string | number;
}

export interface JobFlow {
  nodeId: string;
  isFlowNode: boolean;
  flowRoot: FlowNode | null;
}

export interface FlowNode {
  id: string;
  name: string;
  state: string;
  progress: string | boolean | number | object;
  queueName: string;
  children: FlowNode[];
}

export type QueueType = 'bull' | 'bullmq';

export interface AppQueue {
  delimiter: string;
  name: string;
  displayName?: string;
  description?: string;
  counts: Record<Status, number>;
  jobs: AppJob[];
  statuses: Status[];
  pagination: Pagination;
  readOnlyMode: boolean;
  allowRetries: boolean;
  allowCompletedRetries: boolean;
  isPaused: boolean;
  type: QueueType;
  globalConcurrency: number | null;
}

export type HTTPMethod = 'get' | 'post' | 'put' | 'patch';
export type HTTPStatus = 200 | 204 | 400 | 403 | 404 | 405 | 500;

export interface BullBoardRequest {
  queues: BullBoardQueues;
  uiConfig: UIConfig;
  query: Record<string, any>;
  params: Record<string, any>;
  body: Record<string, any>;
  headers: Record<string, string | undefined>;
}

export type ControllerHandlerReturnType = {
  status?: HTTPStatus;
  body: string | Record<string, any>;
};

export type ViewHandlerReturnType = {
  name: string;
  params: Record<string, string>;
};

export type Promisify<T> = T | Promise<T>;

export interface AppControllerRoute {
  method: HTTPMethod | HTTPMethod[];
  route: string | string[];

  handler(request?: BullBoardRequest): Promisify<ControllerHandlerReturnType>;
}

export interface AppViewRoute {
  method: HTTPMethod;
  route: string | string[];

  handler(params: { basePath: string; uiConfig: UIConfig }): ViewHandlerReturnType;
}

export type AppRouteDefs = {
  entryPoint: AppViewRoute;
  api: AppControllerRoute[];
};

export interface IServerAdapter {
  setQueues(bullBoardQueues: BullBoardQueues): IServerAdapter;

  setViewsPath(viewPath: string): IServerAdapter;

  setStaticPath(staticsRoute: string, staticsPath: string): IServerAdapter;

  setEntryRoute(route: AppViewRoute): IServerAdapter;

  setErrorHandler(handler: (error: Error) => ControllerHandlerReturnType): IServerAdapter;

  setApiRoutes(routes: AppControllerRoute[]): IServerAdapter;

  setUIConfig(config: UIConfig): IServerAdapter;
}

export interface Pagination {
  pageCount: number;
  range: {
    start: number;
    end: number;
  };
}

export type FormatterField = 'data' | 'returnValue' | 'name' | 'progress';

export type BoardOptions = {
  uiBasePath?: string;
  uiConfig?: UIConfig;
  historyProvider?: MetricsHistoryProvider;
};

export type IMiscLink = {
  text: string;
  url: string;
};

export type UIConfig = Partial<{
  boardTitle: string;
  boardLogo: { path: string; width?: number | string; height?: number | string };
  miscLinks: Array<IMiscLink>;
  /** Hide the header Docs icon that links to the bull-board documentation site. Default: false (shown). */
  hideDocsLink: boolean;
  queueSortOptions: Array<{ key: string; label: string }>;
  favIcon: FavIcon;
  locale: { lng?: string };
  dateFormats?: DateFormats;
  pollingInterval?: Partial<{
    showSetting: boolean;
    forceInterval: number;
  }>;
  menu?: { width?: string };
  overview?: { groupByDelimiter?: boolean };
  sortQueues?: boolean;
  hideRedisDetails?: boolean;
  showMetrics?: boolean;
  /** Set by createBullBoard when a historyProvider is configured. Enables the history range selector in the UI. */
  hasHistoryProvider?: boolean;
  /** Set by createBullBoard when the provider reports storage usage. Enables the storage panel. */
  hasHistoryUsage?: boolean;
  /** Set by createBullBoard when the provider can purge and the board is not read-only. */
  canPurgeHistory?: boolean;
  environment?: {
    label: string;
    color: string;
    textColor?: string;
    fontSize?: string | number;
  };
}>;

export type FavIcon = {
  default: string;
  alternative: string;
};

export type DateFormats = {
  /**
   * When timestamp is in same day (today)
   *
   * @example `{ hour: 'numeric', minute: 'numeric', second: 'numeric' }`
   * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat
   */
  short?: Intl.DateTimeFormatOptions;

  /**
   * When timestamp is in same year
   *
   * @example `{ month: 'numeric', day: 'numeric', hour: 'numeric', minute: '2-digit', second: '2-digit' }`
   * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat
   */
  common?: Intl.DateTimeFormatOptions;

  /**
   * @example `{ year: 'numeric', month: 'numeric', day: 'numeric', hour: 'numeric', minute: '2-digit', second: '2-digit' }`
   * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat
   */
  full?: Intl.DateTimeFormatOptions;
};
