import {
  localDb,
  type LocalSyncInboxRecord,
  type LocalTaskPriority,
  type LocalTaskRecord,
  type LocalTaskStatus
} from "@/services/local-db";
import { listPendingRemoteOperations } from "@/services/local-sync-repo";
import {
  decryptTaskRecord,
  encryptTaskRecord,
  shouldEncryptTaskRecord
} from "@/services/local-sensitive-codec";

const TASK_PRIORITY_VALUES: LocalTaskPriority[] = ["LOW", "MEDIUM", "HIGH", "URGENT"];
const TASK_STATUS_VALUES: LocalTaskStatus[] = ["TODO", "IN_PROGRESS", "DONE", "ARCHIVED"];

type RemoteTaskPayload = {
  userId?: unknown;
  title?: unknown;
  contentJson?: unknown;
  contentText?: unknown;
  priority?: unknown;
  status?: unknown;
  ddlAt?: unknown;
  version?: unknown;
  createdAt?: unknown;
  updatedAt?: unknown;
  deletedAt?: unknown;
};

function normalizePriority(value: unknown, fallback: LocalTaskPriority): LocalTaskPriority {
  if (typeof value === "string" && TASK_PRIORITY_VALUES.includes(value as LocalTaskPriority)) {
    return value as LocalTaskPriority;
  }

  return fallback;
}

function normalizeStatus(value: unknown, fallback: LocalTaskStatus): LocalTaskStatus {
  if (typeof value === "string" && TASK_STATUS_VALUES.includes(value as LocalTaskStatus)) {
    return value as LocalTaskStatus;
  }

  return fallback;
}

function normalizeStringOrNull(value: unknown, fallback: string | null): string | null {
  if (typeof value === "string") {
    return value;
  }

  if (value === null) {
    return null;
  }

  return fallback;
}

function collectTextFromRichContent(value: unknown, fragments: string[]): void {
  if (!value || typeof value !== "object") {
    return;
  }

  const node = value as {
    text?: unknown;
    content?: unknown;
  };

  if (typeof node.text === "string" && node.text.trim().length > 0) {
    fragments.push(node.text.trim());
  }

  if (Array.isArray(node.content)) {
    for (const child of node.content) {
      collectTextFromRichContent(child, fragments);
    }
  }
}

function extractTextFromContentJson(contentJson: string | null): string | null {
  if (!contentJson) {
    return null;
  }

  try {
    const parsed = JSON.parse(contentJson) as unknown;
    const fragments: string[] = [];
    collectTextFromRichContent(parsed, fragments);
    return fragments.length > 0 ? fragments.join(" ") : null;
  } catch {
    return null;
  }
}

function normalizeNullableNumber(value: unknown, fallback: number | null): number | null {
  if (typeof value === "number" && Number.isFinite(value)) {
    return value;
  }

  if (value === null) {
    return null;
  }

  return fallback;
}

function normalizePositiveNumber(value: unknown, fallback: number): number {
  if (typeof value === "number" && Number.isFinite(value) && value > 0) {
    return value;
  }

  return fallback;
}

function parseOperationPayload(operation: LocalSyncInboxRecord): RemoteTaskPayload {
  if (!operation.payload) {
    return {};
  }

  const parsed = JSON.parse(operation.payload) as unknown;
  if (!parsed || typeof parsed !== "object") {
    return {};
  }

  return parsed as RemoteTaskPayload;
}

function createFallbackTask(
  operation: LocalSyncInboxRecord,
  userId: string,
  updatedAt: number,
  version: number
): LocalTaskRecord {
  return {
    id: operation.entityId,
    userId,
    title: "未命名任务",
    contentJson: null,
    contentText: null,
    priority: "MEDIUM",
    status: "TODO",
    ddlAt: null,
    version,
    createdAt: updatedAt,
    updatedAt,
    deletedAt: null
  };
}

function buildIncomingTaskRecord(
  operation: LocalSyncInboxRecord,
  currentTask: LocalTaskRecord | undefined
): LocalTaskRecord {
  const payload = parseOperationPayload(operation);
  const fallbackVersion = currentTask?.version ?? 1;
  const version = normalizePositiveNumber(payload.version, fallbackVersion);
  const updatedAt = normalizePositiveNumber(
    payload.updatedAt,
    normalizePositiveNumber(payload.deletedAt, operation.clientTs)
  );
  const fallbackTask =
    currentTask ?? createFallbackTask(operation, operation.userId, updatedAt, version);
  const contentJson = normalizeStringOrNull(payload.contentJson, fallbackTask.contentJson);
  const contentText = normalizeStringOrNull(
    payload.contentText,
    extractTextFromContentJson(contentJson) ?? fallbackTask.contentText
  );

  if (operation.action === "DELETE") {
    const deletedAt = normalizePositiveNumber(payload.deletedAt, updatedAt);
    return {
      ...fallbackTask,
      version,
      updatedAt: deletedAt,
      deletedAt
    };
  }

  return {
    ...fallbackTask,
    userId: typeof payload.userId === "string" ? payload.userId : fallbackTask.userId,
    title:
      typeof payload.title === "string" && payload.title.trim().length > 0
        ? payload.title
        : fallbackTask.title,
    contentJson,
    contentText,
    priority: normalizePriority(payload.priority, fallbackTask.priority),
    status: normalizeStatus(payload.status, fallbackTask.status),
    ddlAt: normalizeNullableNumber(payload.ddlAt, fallbackTask.ddlAt),
    version,
    createdAt: normalizePositiveNumber(payload.createdAt, fallbackTask.createdAt),
    updatedAt,
    deletedAt: normalizeNullableNumber(payload.deletedAt, null)
  };
}

function getOperationTieBreaker(operation: LocalSyncInboxRecord): number {
  if (operation.action === "DELETE") {
    return 3;
  }

  if (operation.action === "UPDATE") {
    return 2;
  }

  return 1;
}

function shouldApplyIncomingTask(
  currentTask: LocalTaskRecord | undefined,
  incomingTask: LocalTaskRecord,
  operation: LocalSyncInboxRecord
): boolean {
  if (!currentTask) {
    return true;
  }

  if (incomingTask.updatedAt > currentTask.updatedAt) {
    return true;
  }

  if (incomingTask.updatedAt < currentTask.updatedAt) {
    return false;
  }

  if (incomingTask.version > currentTask.version) {
    return true;
  }

  if (incomingTask.version < currentTask.version) {
    return false;
  }

  return getOperationTieBreaker(operation) >= (currentTask.deletedAt === null ? 1 : 3);
}

export async function applyPendingRemoteOperations(userId: string): Promise<number> {
  const pendingOperations = await listPendingRemoteOperations(userId);
  if (pendingOperations.length === 0) {
    return 0;
  }

  const appliedAt = Date.now();

  await localDb.transaction("rw", localDb.tasks, localDb.syncInbox, async () => {
    for (const operation of pendingOperations) {
      if (operation.entityType !== "TASK") {
        await localDb.syncInbox.update(operation.opId, { appliedAt });
        continue;
      }

      const storedTask = await localDb.tasks.get(operation.entityId);
      const currentTask = storedTask ? await decryptTaskRecord(storedTask) : undefined;
      const incomingTask = buildIncomingTaskRecord(operation, currentTask);

      if (shouldApplyIncomingTask(currentTask, incomingTask, operation)) {
        await localDb.tasks.put(await encryptTaskRecord(incomingTask));
      } else if (storedTask && currentTask && shouldEncryptTaskRecord(storedTask)) {
        await localDb.tasks.put(await encryptTaskRecord(currentTask));
      }

      await localDb.syncInbox.update(operation.opId, { appliedAt });
    }
  });

  return pendingOperations.length;
}
