import { api, ApiRequestError } from './api';

export type ServerDataKey =
  | 'products-overview'
  | 'requirements'
  | 'version-plans'
  | 'dev-tasks'
  | 'test-cases'
  | 'bugs'
  | 'members'
  | 'task-categories'
  | 'task-worklogs'
  | 'work-activities'
  | 'xiaobao-risk-insights'
  | 'xiaobao-risk-snapshots'
  | 'xiaobao-warning-views'
  | 'overtime';

interface ServerDataResponse<T> {
  key: ServerDataKey;
  value: T | null;
  version: string | null;
}

type ServerDataConflictBody<T> = {
  code: 'APP_DATA_CONFLICT';
  key: ServerDataKey;
  currentValue: T | null;
  currentVersion: string | null;
};

type ServerDataWriteFrozenBody = {
  code: 'APP_DATA_WRITE_FROZEN';
  key: ServerDataKey;
  state: 'write_frozen' | 'read_only_archive';
  replacement: string;
  note?: string;
};

const serverDataVersions = new Map<ServerDataKey, string | null>();
const serverDataCache = new Map<ServerDataKey, {
  value: unknown | null;
  version: string | null;
  loadedAt: number;
}>();
const serverDataLoadPromises = new Map<ServerDataKey, Promise<unknown | null>>();
const serverDataSaveQueues = new Map<ServerDataKey, Promise<void>>();
export const SERVER_DATA_CACHE_MS = 30_000;
export const SERVER_DATA_SAVE_ERROR_EVENT = 'ftb:server-data-save-error';

export type ServerDataSaveErrorDetail = {
  key: ServerDataKey;
  message: string;
};

type LoadServerDataOptions = {
  force?: boolean;
  maxAgeMs?: number;
};

export class ServerDataConflictError<T = unknown> extends Error {
  constructor(
    public readonly key: ServerDataKey,
    public readonly currentValue: T | null,
    public readonly currentVersion: string | null,
  ) {
    super('Server data was changed by another user. Reload before saving again.');
    this.name = 'ServerDataConflictError';
  }
}

export class ServerDataWriteFrozenError extends Error {
  constructor(
    public readonly key: ServerDataKey,
    public readonly state: 'write_frozen' | 'read_only_archive',
    public readonly replacement: string,
    public readonly note?: string,
  ) {
    super(`AppData 写入已冻结，请改用 ${replacement}`);
    this.name = 'ServerDataWriteFrozenError';
  }
}

function getServerDataSaveErrorMessage(error: unknown) {
  if (error instanceof ServerDataConflictError) return '数据已被其他用户更新，请刷新后重试';
  if (error instanceof ServerDataWriteFrozenError) return error.message;
  if (error instanceof Error && error.message) return error.message;
  return '保存失败，请检查网络或 API 服务';
}

function notifyServerDataSaveFailure(key: ServerDataKey, error: unknown) {
  if (typeof window === 'undefined') return;
  window.dispatchEvent(
    new CustomEvent(SERVER_DATA_SAVE_ERROR_EVENT, {
      detail: {
        key,
        message: getServerDataSaveErrorMessage(error),
      },
    }),
  );
}

export async function loadServerData<T>(
  key: ServerDataKey,
  options: LoadServerDataOptions = {},
): Promise<T | null> {
  const maxAgeMs = options.maxAgeMs ?? SERVER_DATA_CACHE_MS;
  const cached = serverDataCache.get(key);
  if (!options.force && cached && maxAgeMs > 0 && Date.now() - cached.loadedAt <= maxAgeMs) {
    return cached.value as T | null;
  }

  const inFlight = serverDataLoadPromises.get(key);
  if (inFlight) return inFlight as Promise<T | null>;

  const promise = api.get<ServerDataResponse<T>>(`/data/${key}`)
    .then((res) => {
      const version = res.version ?? null;
      serverDataVersions.set(key, version);
      serverDataCache.set(key, {
        value: res.value,
        version,
        loadedAt: Date.now(),
      });
      return res.value;
    })
    .finally(() => {
      serverDataLoadPromises.delete(key);
    });

  serverDataLoadPromises.set(key, promise as Promise<unknown | null>);
  return promise;
}

export function saveServerData<T>(key: ServerDataKey, value: T): Promise<void> {
  const previousSave = serverDataSaveQueues.get(key) ?? Promise.resolve();
  const save = previousSave.then(() => putServerData(key, value));
  const queueTail = save.catch(() => undefined);
  serverDataSaveQueues.set(key, queueTail);
  void queueTail.then(() => {
    if (serverDataSaveQueues.get(key) === queueTail) {
      serverDataSaveQueues.delete(key);
    }
  });
  return save;
}

async function putServerData<T>(key: ServerDataKey, value: T): Promise<void> {
  const payload: { value: T; version?: string | null } = { value };
  if (serverDataVersions.has(key)) {
    payload.version = serverDataVersions.get(key) ?? null;
  }

  try {
    const res = await api.put<ServerDataResponse<T>>(`/data/${key}`, payload);
    const version = res.version ?? null;
    serverDataVersions.set(key, version);
    serverDataCache.set(key, {
      value,
      version,
      loadedAt: Date.now(),
    });
  } catch (error) {
    if (
      error instanceof ApiRequestError &&
      error.status === 409 &&
      isServerDataConflictBody<T>(error.body, key)
    ) {
      const conflict = new ServerDataConflictError(
        error.body.key,
        error.body.currentValue,
        error.body.currentVersion,
      );
      notifyServerDataSaveFailure(key, conflict);
      throw conflict;
    }
    if (
      error instanceof ApiRequestError &&
      error.status === 409 &&
      isServerDataWriteFrozenBody(error.body, key)
    ) {
      const frozen = new ServerDataWriteFrozenError(
        error.body.key,
        error.body.state,
        error.body.replacement,
        error.body.note,
      );
      notifyServerDataSaveFailure(key, frozen);
      throw frozen;
    }
    notifyServerDataSaveFailure(key, error);
    throw error;
  }
}

function isServerDataConflictBody<T>(
  body: unknown,
  key: ServerDataKey,
): body is ServerDataConflictBody<T> {
  return (
    typeof body === 'object' &&
    body !== null &&
    (body as { code?: unknown }).code === 'APP_DATA_CONFLICT' &&
    (body as { key?: unknown }).key === key &&
    ('currentVersion' in body
      ? typeof (body as { currentVersion?: unknown }).currentVersion === 'string' ||
        (body as { currentVersion?: unknown }).currentVersion === null
      : true)
  );
}

function isServerDataWriteFrozenBody(
  body: unknown,
  key: ServerDataKey,
): body is ServerDataWriteFrozenBody {
  return (
    typeof body === 'object' &&
    body !== null &&
    (body as { code?: unknown }).code === 'APP_DATA_WRITE_FROZEN' &&
    (body as { key?: unknown }).key === key &&
    ((body as { state?: unknown }).state === 'write_frozen' ||
      (body as { state?: unknown }).state === 'read_only_archive') &&
    typeof (body as { replacement?: unknown }).replacement === 'string'
  );
}
