import type { Message } from '../../../../shared-kernel';
import type { MessageRevisionType } from '../../../../shared-kernel/message-revision.models';
import { getMessageTimestamp } from './message.rules';

export interface MessageHeadState {
  messageId: string;
  revision: number;
  senderId: string;
  content: string;
  isDeleted: boolean;
  editedAt: number;
  channelId: string;
  replyToId: string;
}

export interface InventoryIntegritySnapshot {
  ts: number;
  rc: number;
  ac: number;
  revision: number;
  headHash: string;
}

export interface RemoteInventoryItem {
  id: string;
  ts: number;
  rc?: number;
  ac?: number;
  revision?: number;
  headHash?: string;
}

export type MessageRevisionAction = MessageRevisionType;

export function getMessageRevision(message: Pick<Message, 'revision'> | null | undefined): number {
  return typeof message?.revision === 'number' && message.revision >= 0
    ? message.revision
    : 0;
}

export function resolveMessageRevision(
  existing: Pick<Message, 'revision'> | null | undefined,
  _action: MessageRevisionAction
): number {
  return getMessageRevision(existing ?? undefined) + (existing ? 1 : 0);
}

export function buildMessageHeadState(message: Message, revision = getMessageRevision(message)): MessageHeadState {
  return {
    messageId: message.id,
    revision,
    senderId: message.senderId,
    content: message.isDeleted ? '' : message.content,
    isDeleted: message.isDeleted,
    editedAt: getMessageTimestamp(message),
    channelId: message.channelId ?? 'general',
    replyToId: message.replyToId ?? ''
  };
}

export async function computeMessageHeadHash(state: MessageHeadState): Promise<string> {
  const canonical = JSON.stringify(state, Object.keys(state).sort());

  if (typeof globalThis.crypto?.subtle?.digest !== 'function') {
    throw new Error('Web Crypto digest is unavailable for message integrity hashing');
  }

  const digest = await globalThis.crypto.subtle.digest(
    'SHA-256',
    new TextEncoder().encode(canonical)
  );

  return Array.from(new Uint8Array(digest))
    .map((byte) => byte.toString(16).padStart(2, '0'))
    .join('');
}

export async function computeMessageHeadHashFromMessage(
  message: Message,
  revision = getMessageRevision(message)
): Promise<string> {
  return await computeMessageHeadHash(buildMessageHeadState(message, revision));
}

function hasIntegrityFields(item: RemoteInventoryItem): item is RemoteInventoryItem & {
  revision: number;
  headHash: string;
} {
  return typeof item.revision === 'number'
    && typeof item.headHash === 'string'
    && item.headHash.length > 0;
}

export function inventoryNeedsRefresh(
  remote: RemoteInventoryItem,
  local: InventoryIntegritySnapshot
): boolean {
  if (!hasIntegrityFields(remote)) {
    return remote.ts > local.ts
      || (remote.rc !== undefined && remote.rc !== local.rc)
      || (remote.ac !== undefined && remote.ac !== local.ac);
  }

  if (remote.revision > local.revision) {
    return true;
  }

  if (remote.revision < local.revision) {
    return false;
  }

  if (remote.headHash !== local.headHash) {
    return true;
  }

  return remote.ts > local.ts
    || (remote.rc !== undefined && remote.rc !== local.rc)
    || (remote.ac !== undefined && remote.ac !== local.ac);
}

export function shouldApplyIncomingRevision(
  incomingRevision: number,
  existingRevision: number,
  incomingHeadHash: string,
  existingHeadHash: string
): boolean {
  if (incomingRevision > existingRevision) {
    return true;
  }

  if (incomingRevision < existingRevision) {
    return false;
  }

  return incomingHeadHash !== existingHeadHash;
}
