import { getPairedTurnOutputs } from './db.js';
import {
  resolveStoredVisibleVerdict,
  type VisibleVerdict,
} from './paired-verdict.js';
import {
  matchesExpectedPairedFollowUpIntent,
  resolveFollowUpDispatch,
  resolveNextTurnAction,
  shouldRetrySilentOwnerExecution,
  type FollowUpDispatch,
  type NextTurnAction,
} from './message-runtime-rules.js';
import {
  schedulePairedFollowUpOnce,
  type ScheduledPairedFollowUpIntentKind,
} from './paired-follow-up-scheduler.js';
import type { PairedRoomRole, PairedTask, PairedTaskStatus } from './types.js';

export type PairedFollowUpSource = Parameters<
  typeof resolveFollowUpDispatch
>[0]['source'];

type PairedTurnOutputContext = ReturnType<typeof getPairedTurnOutputs>[number];

type PairedFollowUpTaskContext = Pick<
  PairedTask,
  'id' | 'status' | 'round_trip_count' | 'updated_at' | 'owner_failure_count'
>;

function timestampMs(value: string | null | undefined): number | null {
  if (!value) {
    return null;
  }
  const parsed = Date.parse(value);
  return Number.isFinite(parsed) ? parsed : null;
}

function isPersistedOutputOlderThanTaskRevision(args: {
  output: PairedTurnOutputContext;
  task: Partial<Pick<PairedTask, 'updated_at'>> | null | undefined;
}): boolean {
  const outputCreatedAt = timestampMs(args.output.created_at);
  const taskUpdatedAt = timestampMs(args.task?.updated_at);
  if (outputCreatedAt == null || taskUpdatedAt == null) {
    return false;
  }
  return outputCreatedAt < taskUpdatedAt;
}

function shouldPreferFallbackTurnOutputContext(args: {
  output: PairedTurnOutputContext | null | undefined;
  task: Partial<Pick<PairedTask, 'updated_at'>> | null | undefined;
  fallbackRole: PairedRoomRole | null | undefined;
}): boolean {
  if (!args.output || !args.fallbackRole) {
    return false;
  }
  if (args.output.role === args.fallbackRole) {
    return false;
  }
  return isPersistedOutputOlderThanTaskRevision({
    output: args.output,
    task: args.task,
  });
}

export interface PairedFollowUpDecision {
  taskId: string | null;
  taskStatus: PairedTaskStatus | null;
  lastTurnOutputRole: PairedRoomRole | null;
  lastTurnOutputVerdict: VisibleVerdict | null;
  nextTurnAction: NextTurnAction;
  dispatch: FollowUpDispatch;
}

export type PairedFollowUpDispatchResult =
  | (PairedFollowUpDecision & {
      kind: 'none';
    })
  | (PairedFollowUpDecision & {
      kind: 'message-check';
    })
  | (PairedFollowUpDecision & {
      kind: 'paired-follow-up';
      intentKind: ScheduledPairedFollowUpIntentKind;
      scheduled: boolean;
    });

export function resolveLatestPairedTurnOutputContext(args: {
  task:
    | (Pick<PairedTask, 'id'> & Partial<Pick<PairedTask, 'updated_at'>>)
    | null
    | undefined;
  fallbackLastTurnOutputRole?: PairedRoomRole | null;
  fallbackLastTurnOutputVerdict?: VisibleVerdict | null;
}): {
  role: PairedRoomRole | null;
  verdict: VisibleVerdict | null;
} {
  const latestOutput = args.task
    ? getPairedTurnOutputs(args.task.id).at(-1)
    : null;
  const output = shouldPreferFallbackTurnOutputContext({
    output: latestOutput,
    task: args.task,
    fallbackRole: args.fallbackLastTurnOutputRole,
  })
    ? null
    : latestOutput;
  return {
    role: output?.role ?? args.fallbackLastTurnOutputRole ?? null,
    verdict:
      resolveStoredVisibleVerdict({
        verdict: output?.verdict ?? null,
        outputText: output?.output_text ?? null,
      }) ??
      args.fallbackLastTurnOutputVerdict ??
      null,
  };
}

export function resolvePairedFollowUpDecision(args: {
  task:
    | Pick<
        PairedFollowUpTaskContext,
        'id' | 'status' | 'updated_at' | 'owner_failure_count'
      >
    | null
    | undefined;
  source: PairedFollowUpSource;
  completedRole?: PairedRoomRole;
  executionStatus?: 'succeeded' | 'failed';
  sawOutput?: boolean;
  fallbackLastTurnOutputRole?: PairedRoomRole | null;
  fallbackLastTurnOutputVerdict?: VisibleVerdict | null;
}): PairedFollowUpDecision {
  const { role: lastTurnOutputRole, verdict: lastTurnOutputVerdict } =
    resolveLatestPairedTurnOutputContext({
      task: args.task,
      fallbackLastTurnOutputRole: args.fallbackLastTurnOutputRole,
      fallbackLastTurnOutputVerdict: args.fallbackLastTurnOutputVerdict,
    });
  const silentOwnerRetry = shouldRetrySilentOwnerExecution({
    completedRole: args.completedRole,
    executionStatus: args.executionStatus,
    sawOutput: args.sawOutput,
    taskStatus: args.task?.status ?? null,
  });
  const nextTurnAction = silentOwnerRetry
    ? ({ kind: 'owner-follow-up' } as const)
    : resolveNextTurnAction({
        taskStatus: args.task?.status ?? null,
        lastTurnOutputRole,
        lastTurnOutputVerdict,
        ownerFailureCount: args.task?.owner_failure_count ?? null,
      });
  const dispatch = resolveFollowUpDispatch({
    source: args.source,
    nextTurnAction,
    completedRole: args.completedRole,
    executionStatus: args.executionStatus,
    sawOutput: args.sawOutput,
  });

  return {
    taskId: args.task?.id ?? null,
    taskStatus: args.task?.status ?? null,
    lastTurnOutputRole,
    lastTurnOutputVerdict,
    nextTurnAction,
    dispatch,
  };
}

export function schedulePairedFollowUpIntent(args: {
  chatJid: string;
  runId: string;
  task: PairedFollowUpTaskContext | null | undefined;
  intentKind: ScheduledPairedFollowUpIntentKind;
  enqueue: () => void;
  fallbackLastTurnOutputRole?: PairedRoomRole | null;
  fallbackLastTurnOutputVerdict?: VisibleVerdict | null;
  lastTurnOutputRole?: PairedRoomRole | null;
  lastTurnOutputVerdict?: VisibleVerdict | null;
  allowSilentOwnerRetry?: boolean;
}): boolean {
  if (!args.task) {
    return false;
  }

  const latestOutputContext =
    args.lastTurnOutputRole != null || args.lastTurnOutputVerdict != null
      ? {
          role: args.lastTurnOutputRole ?? null,
          verdict: args.lastTurnOutputVerdict ?? null,
        }
      : resolveLatestPairedTurnOutputContext({
          task: args.task,
          fallbackLastTurnOutputRole: args.fallbackLastTurnOutputRole,
          fallbackLastTurnOutputVerdict: args.fallbackLastTurnOutputVerdict,
        });

  if (
    !matchesExpectedPairedFollowUpIntent({
      taskStatus: args.task.status,
      lastTurnOutputRole: latestOutputContext.role,
      lastTurnOutputVerdict: latestOutputContext.verdict,
      ownerFailureCount: args.task.owner_failure_count ?? null,
      intentKind: args.intentKind,
    }) &&
    !(
      args.allowSilentOwnerRetry === true &&
      args.task.status === 'active' &&
      args.intentKind === 'owner-follow-up'
    )
  ) {
    return false;
  }

  return schedulePairedFollowUpOnce({
    chatJid: args.chatJid,
    runId: args.runId,
    task: args.task,
    intentKind: args.intentKind,
    enqueue: args.enqueue,
  });
}

export function schedulePairedFollowUpWithMessageCheck(args: {
  chatJid: string;
  runId: string;
  task: PairedFollowUpTaskContext | null | undefined;
  intentKind: ScheduledPairedFollowUpIntentKind;
  enqueueMessageCheck: () => void;
  fallbackLastTurnOutputRole?: PairedRoomRole | null;
  fallbackLastTurnOutputVerdict?: VisibleVerdict | null;
  lastTurnOutputRole?: PairedRoomRole | null;
  lastTurnOutputVerdict?: VisibleVerdict | null;
}): boolean {
  return schedulePairedFollowUpIntent({
    chatJid: args.chatJid,
    runId: args.runId,
    task: args.task,
    intentKind: args.intentKind,
    enqueue: args.enqueueMessageCheck,
    fallbackLastTurnOutputRole: args.fallbackLastTurnOutputRole,
    fallbackLastTurnOutputVerdict: args.fallbackLastTurnOutputVerdict,
    lastTurnOutputRole: args.lastTurnOutputRole,
    lastTurnOutputVerdict: args.lastTurnOutputVerdict,
  });
}

export function dispatchPairedFollowUpForEvent(args: {
  chatJid: string;
  runId: string;
  task: PairedFollowUpTaskContext | null | undefined;
  source: PairedFollowUpSource;
  completedRole?: PairedRoomRole;
  executionStatus?: 'succeeded' | 'failed';
  sawOutput?: boolean;
  fallbackLastTurnOutputRole?: PairedRoomRole | null;
  fallbackLastTurnOutputVerdict?: VisibleVerdict | null;
  enqueue: () => void;
  enqueueMessageCheck?: () => void;
}): PairedFollowUpDispatchResult {
  const decision = resolvePairedFollowUpDecision({
    task: args.task,
    source: args.source,
    completedRole: args.completedRole,
    executionStatus: args.executionStatus,
    sawOutput: args.sawOutput,
    fallbackLastTurnOutputRole: args.fallbackLastTurnOutputRole,
    fallbackLastTurnOutputVerdict: args.fallbackLastTurnOutputVerdict,
  });

  if (
    decision.dispatch.kind === 'enqueue' &&
    decision.dispatch.queueKind === 'message-check'
  ) {
    args.enqueueMessageCheck?.();
    return {
      kind: 'message-check',
      ...decision,
    };
  }

  if (
    decision.dispatch.kind === 'enqueue' &&
    decision.dispatch.queueKind === 'paired-follow-up' &&
    decision.nextTurnAction.kind !== 'none'
  ) {
    const scheduled = schedulePairedFollowUpIntent({
      chatJid: args.chatJid,
      runId: args.runId,
      task: args.task,
      intentKind: decision.nextTurnAction.kind,
      enqueue: args.enqueue,
      lastTurnOutputRole: decision.lastTurnOutputRole,
      lastTurnOutputVerdict: decision.lastTurnOutputVerdict,
      allowSilentOwnerRetry: shouldRetrySilentOwnerExecution({
        completedRole: args.completedRole,
        executionStatus: args.executionStatus,
        sawOutput: args.sawOutput,
        taskStatus: args.task?.status ?? null,
      }),
    });

    return {
      kind: 'paired-follow-up',
      ...decision,
      intentKind: decision.nextTurnAction.kind,
      scheduled,
    };
  }

  return {
    kind: 'none',
    ...decision,
  };
}

export function enqueuePairedFollowUpAfterEvent(args: {
  chatJid: string;
  runId: string;
  task: PairedFollowUpTaskContext | null | undefined;
  source: PairedFollowUpSource;
  completedRole?: PairedRoomRole;
  executionStatus?: 'succeeded' | 'failed';
  sawOutput?: boolean;
  fallbackLastTurnOutputRole?: PairedRoomRole | null;
  fallbackLastTurnOutputVerdict?: VisibleVerdict | null;
  enqueueMessageCheck: () => void;
}): PairedFollowUpDispatchResult {
  return dispatchPairedFollowUpForEvent({
    chatJid: args.chatJid,
    runId: args.runId,
    task: args.task,
    source: args.source,
    completedRole: args.completedRole,
    executionStatus: args.executionStatus,
    sawOutput: args.sawOutput,
    fallbackLastTurnOutputRole: args.fallbackLastTurnOutputRole,
    fallbackLastTurnOutputVerdict: args.fallbackLastTurnOutputVerdict,
    enqueue: args.enqueueMessageCheck,
    enqueueMessageCheck: args.enqueueMessageCheck,
  });
}

export function requeuePendingPairedTurn(args: {
  schedulePairedFollowUp: () => boolean;
  closeStdin: () => void;
}): boolean {
  const scheduled = args.schedulePairedFollowUp();

  if (scheduled) {
    args.closeStdin();
  }

  return scheduled;
}
