import type {
  WebCanvasWorkflow,
  WebCanvasWorkflowNode,
  WebEnterpriseUsageSummary,
  WebProjectSummary,
  WebUsageSummary,
  WebUserSession,
} from "../types";
import {
  getErrorMessage,
  getServerBaseUrl,
  isRecord,
  isServerRequestError,
  readStoredSession,
  serverRequest,
  unwrapApiPayload,
  writeStoredSession,
} from "./serverConnection";

interface LoginInput {
  username: string;
  password: string;
}

interface RegisterInput extends LoginInput {
  betaCode: string;
}

interface EmailAuthInput {
  email: string;
  password: string;
  username?: string;
  code?: string;
  betaCode?: string;
}

interface EmailCodeInput {
  email: string;
  code: string;
  purpose?: "register" | "login";
}

interface ForgotPasswordInput {
  email: string;
}

interface ResetPasswordInput {
  email: string;
  code: string;
  newPassword: string;
}

interface PhoneAuthInput {
  phone: string;
  code: string;
  password?: string;
  betaCode?: string;
}

interface UpdateProfileInput {
  avatarUrl?: string | null;
  avatarOssKey?: string | null;
  bio?: string | null;
  backgroundUrl?: string | null;
  profileBackgroundUrl?: string | null;
}

interface DeleteProjectOptions {
  cleanupUserData?: boolean;
}

export interface RechargeOrderInput {
  planId: string;
  paymentMethod: "wechat" | "alipay" | "bank";
}

export interface RechargeOrderResult {
  orderId: string;
  status: string;
  payUrl?: string | null;
  qrCodeUrl?: string | null;
  message?: string | null;
}

export interface WechatLoginTicket {
  configured: boolean;
  url?: string;
  state?: string;
  message?: string;
}

export interface WechatLoginSessionStatus {
  status: "pending" | "completed" | "failed" | "expired" | "missing" | "consumed" | string;
  error?: string;
  session?: WebUserSession;
}

const getBaseUrl = getServerBaseUrl;
const request = serverRequest;
const isHttpError = isServerRequestError;
const PROJECT_CONTENT_ENRICH_CONCURRENCY = 1;
let projectContentEnrichmentDisabled = false;

function toNumber(value: unknown, fallback = 0): number {
  const numberValue = typeof value === "number" ? value : Number(value);
  return Number.isFinite(numberValue) ? numberValue : fallback;
}

function toStringValue(value: unknown, fallback = ""): string {
  if (typeof value === "string") {
    const trimmed = value.trim();
    return trimmed || fallback;
  }
  if (typeof value === "number" && Number.isFinite(value)) {
    return String(value);
  }
  return fallback;
}

function toNullableString(value: unknown): string | null {
  if (typeof value !== "string") return null;
  const trimmed = value.trim();
  return trimmed || null;
}

function toNullableId(value: unknown): number | string | null {
  if (typeof value === "number" && Number.isFinite(value)) return value;
  return toNullableString(value);
}

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

function isPlaceholderProjectText(value: string | null | undefined): boolean {
  if (!value) return true;
  return /^(新建项目|新建创作|未命名项目|Untitled project)$/i.test(value.trim());
}

function isPlaceholderProjectDescription(value: string | null | undefined): boolean {
  if (!value) return true;
  return /^(从空白画布开始|从空白画布开始，直接进入节点式创作。|最近更新的项目)$/i.test(value.trim());
}

function chooseProjectText(contentText: string | null, currentText: string | null, fallback: string): string {
  if (contentText && (!isPlaceholderProjectText(contentText) || isPlaceholderProjectText(currentText))) {
    return contentText;
  }
  return currentText || contentText || fallback;
}

function chooseProjectDescription(contentText: string | null, currentText: string | null): string | null {
  if (contentText && (!isPlaceholderProjectDescription(contentText) || isPlaceholderProjectDescription(currentText))) {
    return contentText;
  }
  return currentText || contentText || null;
}

function pickFirstString(...values: unknown[]): string | null {
  for (const value of values) {
    const next = toNullableString(value);
    if (next) return next;
  }
  return null;
}

function toProjectSummary(
  raw: Record<string, unknown>,
  source: WebProjectSummary["source"],
): WebProjectSummary {
  return {
    id: toStringValue(raw.id),
    name: toStringValue(raw.name, "未命名项目"),
    description: toNullableString(raw.description),
    thumbnailUrl: toNullableString(raw.thumbnail_url ?? raw.thumbnailUrl),
    updatedAt: toStringValue(raw.updated_at ?? raw.updatedAt, "刚刚"),
    storyboardCount: toNumber(raw.storyboard_count ?? raw.storyboardCount),
    imageCount: toNumber(raw.image_count ?? raw.imageCount),
    videoCount: toNumber(raw.video_count ?? raw.videoCount),
    source,
  };
}

function buildProjectSummaryFromWorkflow(
  workflow: WebCanvasWorkflow,
  source: WebProjectSummary["source"],
  errorMessage?: string,
): WebProjectSummary {
  const previewNode = workflow.nodes.find((node) => node.previewUrl);
  return {
    id: workflow.id,
    name: workflow.title,
    description: workflow.description,
    thumbnailUrl: previewNode?.previewUrl ?? null,
    updatedAt: "刚刚",
    storyboardCount: workflow.nodes.length,
    imageCount: workflow.nodes.filter((node) => node.kind === "image").length,
    videoCount: workflow.nodes.filter((node) => node.kind === "video").length,
    source,
    ...(errorMessage ? { errorMessage } : {}),
  };
}

function unwrapProjectContentPayload(payload: unknown): unknown {
  const unwrapped = unwrapApiPayload(payload);
  return isRecord(unwrapped) && "content" in unwrapped ? unwrapped.content : unwrapped;
}

function getContentWorkflowRecord(content: unknown): Record<string, unknown> | null {
  if (!isRecord(content)) return null;
  const workflow = content.workflowData ?? content.workflow ?? content.workflow_data;
  return isRecord(workflow) ? workflow : null;
}

function getContentNodes(content: unknown): Record<string, unknown>[] {
  const workflow = getContentWorkflowRecord(content);
  const nodeSource =
    (workflow && Array.isArray(workflow.nodes) ? workflow.nodes : null) ??
    (isRecord(content) && Array.isArray(content.nodes) ? content.nodes : null);
  return Array.isArray(nodeSource) ? nodeSource.filter(isRecord) : [];
}

function getContentArray(content: unknown, key: string): Record<string, unknown>[] {
  if (!isRecord(content)) return [];
  const value = content[key];
  return Array.isArray(value) ? value.filter(isRecord) : [];
}

function countNodesByKind(nodes: Record<string, unknown>[], kind: string): number {
  return nodes.filter((node) => node.kind === kind || node.type === kind).length;
}

function pickProjectPreviewUrl(content: unknown): string | null {
  if (!isRecord(content)) return null;
  const workflow = getContentWorkflowRecord(content);
  const nodes = getContentNodes(content);
  const storyboards = getContentArray(content, "storyboards");
  const videos = getContentArray(content, "videos");

  return (
    pickFirstString(
      content.thumbnailUrl,
      content.thumbnail_url,
      content.coverUrl,
      content.cover_url,
      workflow?.thumbnailUrl,
      workflow?.thumbnail_url,
      nodes.find((node) => pickFirstString(node.previewUrl, node.preview_url, node.imageUrl, node.image_url))
        ?.previewUrl,
      nodes.find((node) => pickFirstString(node.previewUrl, node.preview_url, node.imageUrl, node.image_url))
        ?.preview_url,
      nodes.find((node) => pickFirstString(node.previewUrl, node.preview_url, node.imageUrl, node.image_url))
        ?.imageUrl,
      nodes.find((node) => pickFirstString(node.previewUrl, node.preview_url, node.imageUrl, node.image_url))
        ?.image_url,
      storyboards.find((item) => pickFirstString(item.imageUrl, item.image_url, item.coverUrl, item.cover_url))
        ?.imageUrl,
      storyboards.find((item) => pickFirstString(item.imageUrl, item.image_url, item.coverUrl, item.cover_url))
        ?.image_url,
      storyboards.find((item) => pickFirstString(item.imageUrl, item.image_url, item.coverUrl, item.cover_url))
        ?.coverUrl,
      storyboards.find((item) => pickFirstString(item.imageUrl, item.image_url, item.coverUrl, item.cover_url))
        ?.cover_url,
      videos.find((item) => pickFirstString(item.coverUrl, item.cover_url, item.thumbnailUrl, item.thumbnail_url))
        ?.coverUrl,
      videos.find((item) => pickFirstString(item.coverUrl, item.cover_url, item.thumbnailUrl, item.thumbnail_url))
        ?.cover_url,
      videos.find((item) => pickFirstString(item.coverUrl, item.cover_url, item.thumbnailUrl, item.thumbnail_url))
        ?.thumbnailUrl,
      videos.find((item) => pickFirstString(item.coverUrl, item.cover_url, item.thumbnailUrl, item.thumbnail_url))
        ?.thumbnail_url,
    ) || null
  );
}

function mergeProjectSummaryWithContent(project: WebProjectSummary, payload: unknown): WebProjectSummary {
  const content = unwrapProjectContentPayload(payload);
  if (!isRecord(content)) return project;

  const workflow = getContentWorkflowRecord(content);
  const nodes = getContentNodes(content);
  const storyboards = getContentArray(content, "storyboards");
  const videos = getContentArray(content, "videos");
  const contentTitle = pickFirstString(content.name, content.projectName, content.title, workflow?.title);
  const contentDescription = pickFirstString(
    content.description,
    content.projectDescription,
    content.summary,
    workflow?.description,
  );
  const imageCount =
    countNodesByKind(nodes, "image") ||
    storyboards.filter((item) => pickFirstString(item.imageUrl, item.image_url, item.coverUrl, item.cover_url)).length;
  const videoCount =
    countNodesByKind(nodes, "video") ||
    videos.length ||
    storyboards.filter((item) => pickFirstString(item.videoUrl, item.video_url)).length;
  const storyboardCount = nodes.length || storyboards.length;

  return {
    ...project,
    name: chooseProjectText(contentTitle, project.name, project.name || "未命名项目"),
    description: chooseProjectDescription(contentDescription, project.description ?? null),
    thumbnailUrl: pickProjectPreviewUrl(content) || project.thumbnailUrl || null,
    storyboardCount: storyboardCount || project.storyboardCount,
    imageCount: imageCount || project.imageCount,
    videoCount: videoCount || project.videoCount,
  };
}

async function enrichProjectSummariesWithContent(projects: WebProjectSummary[]): Promise<WebProjectSummary[]> {
  if (projectContentEnrichmentDisabled) return projects;

  const enriched = [...projects];

  for (let index = 0; index < projects.length; index += PROJECT_CONTENT_ENRICH_CONCURRENCY) {
    if (projectContentEnrichmentDisabled) break;
    const batch = projects.slice(index, index + PROJECT_CONTENT_ENRICH_CONCURRENCY);
    const results = await Promise.allSettled(
      batch.map(async (project) => {
        const payload = await request<unknown>(`/projects/${encodeURIComponent(project.id)}/content?resolveMedia=1`);
        return mergeProjectSummaryWithContent(project, payload);
      }),
    );

    results.forEach((result, offset) => {
      const targetIndex = index + offset;
      if (result.status === "fulfilled") {
        enriched[targetIndex] = result.value;
      } else {
        const status = isHttpError(result.reason) ? result.reason.status : undefined;
        if (status === 404 || (typeof status === "number" && status >= 500)) {
          projectContentEnrichmentDisabled = true;
        }
        enriched[targetIndex] = {
          ...enriched[targetIndex],
          errorMessage: getErrorMessage(result.reason),
        };
      }
    });
  }

  return enriched;
}

function createWorkflowFingerprint(workflow: WebCanvasWorkflow): string {
  const payload = JSON.stringify({
    title: workflow.title,
    description: workflow.description,
    settings: workflow.settings,
    nodes: workflow.nodes.map((node) => ({
      id: node.id,
      kind: node.kind,
      label: node.label,
      detail: node.detail,
      previewUrl: node.previewUrl || "",
    })),
    edges: workflow.edges.map((edge) => ({
      id: edge.id,
      source: edge.source,
      target: edge.target,
      label: edge.label || "",
    })),
  });

  let hash = 0x811c9dc5;
  for (let i = 0; i < payload.length; i += 1) {
    hash ^= payload.charCodeAt(i);
    hash = Math.imul(hash, 0x01000193);
  }

  return `wf-${(hash >>> 0).toString(16).padStart(8, "0")}`;
}

function toActivePackages(value: unknown): WebUserSession["user"]["activePackages"] {
  if (!Array.isArray(value)) return undefined;

  const packages: NonNullable<WebUserSession["user"]["activePackages"]> = [];
  for (const entry of value) {
    if (!isRecord(entry)) continue;
    packages.push({
      name: toStringValue(entry.name, "Preview package"),
      expiresAt: toStringValue(entry.expiresAt ?? entry.expires_at, ""),
      remainingImage: toNumber(entry.remainingImage ?? entry.remaining_image),
      remainingVideo: toNumber(entry.remainingVideo ?? entry.remaining_video),
      remainingText: toNumber(entry.remainingText ?? entry.remaining_text),
    });
  }
  return packages;
}

function normalizeUser(raw: unknown): WebUserSession["user"] | null {
  const payload = unwrapApiPayload(raw);
  const candidate = isRecord(payload) && isRecord(payload.user) ? payload.user : payload;
  if (!isRecord(candidate)) return null;

  const id = candidate.id ?? candidate.userId ?? candidate.user_id;
  const username = toStringValue(candidate.username ?? candidate.name, "预览用户");
  if (id === undefined || !username) return null;

  return {
    id: typeof id === "number" && Number.isFinite(id) ? id : toStringValue(id),
    username,
    displayName: toNullableString(candidate.displayName ?? candidate.display_name ?? candidate.nickname ?? candidate.name),
    bio: toNullableString(candidate.bio ?? candidate.profileBio ?? candidate.profile_bio ?? candidate.description ?? candidate.intro),
    avatarUrl: toNullableString(candidate.avatarUrl ?? candidate.avatar_url),
    backgroundUrl: toNullableString(
      candidate.profileBackgroundUrl ??
        candidate.profile_background_url ??
        candidate.backgroundUrl ??
        candidate.background_url ??
        candidate.coverUrl ??
        candidate.cover_url,
    ),
    email: toNullableString(candidate.email),
    emailVerified: candidate.emailVerified === true || candidate.email_verified === true || candidate.email_verified === 1,
    phone: toNullableString(candidate.phone),
    authProvider: toNullableString(candidate.authProvider ?? candidate.auth_provider),
    sessionId: toNullableString(candidate.sessionId ?? candidate.session_id),
    sessionStartedAt: toNullableString(candidate.sessionStartedAt ?? candidate.session_started_at),
    role: toNullableString(candidate.role) ?? undefined,
    accountType: toNullableString(candidate.accountType ?? candidate.account_type) ?? undefined,
    enterpriseId: toNullableString(candidate.enterpriseId ?? candidate.enterprise_id),
    enterpriseName: toNullableString(candidate.enterpriseName ?? candidate.enterprise_name),
    enterpriseRole: toNullableString(candidate.enterpriseRole ?? candidate.enterprise_role) ?? undefined,
    enterpriseAdminUserId: toNullableId(candidate.enterpriseAdminUserId ?? candidate.enterprise_admin_user_id),
    balanceCents: toNumber(
      candidate.balanceCents ?? candidate.balance_cents ?? candidate.userBalanceCents ?? candidate.user_balance_cents,
    ),
    enterpriseBalanceCents: toNumber(
      candidate.enterpriseBalanceCents ??
        candidate.enterprise_balance_cents ??
        candidate.enterpriseBalance ??
        candidate.enterprise_balance,
    ),
    maxConcurrency: toNumber(candidate.maxConcurrency ?? candidate.max_concurrency),
    activePackages: toActivePackages(candidate.activePackages ?? candidate.active_packages),
  };
}

function extractProjectRows(payload: unknown): Record<string, unknown>[] {
  const unwrapped = unwrapApiPayload(payload);
  if (Array.isArray(unwrapped)) {
    return unwrapped.filter(isRecord);
  }
  if (!isRecord(unwrapped)) return [];

  const rows = unwrapped.projects ?? unwrapped.items;
  return Array.isArray(rows) ? rows.filter(isRecord) : [];
}

function isCanvasWorkflow(value: unknown): value is WebCanvasWorkflow {
  if (!isRecord(value)) return false;
  return (
    typeof value.id === "string" &&
    typeof value.title === "string" &&
    typeof value.description === "string" &&
    typeof value.version === "number" && value.version >= 1 &&
    isRecord(value.settings) &&
    Array.isArray(value.nodes) &&
    Array.isArray(value.edges)
  );
}

function isLegacyWorkflowData(value: unknown): value is Record<string, unknown> {
  if (!isRecord(value)) return false;
  return (
    typeof value.version === "number" &&
    Array.isArray(value.nodes) &&
    Array.isArray(value.edges)
  );
}

function migrateLegacyWorkflowData(old: Record<string, unknown>, wrapper: Record<string, unknown>, projectId: string): WebCanvasWorkflow {
  const viewport = isRecord(old.viewport) ? old.viewport : {};
  return {
    id: old.id as string || projectId,
    version: 1,
    title: String(wrapper.name || wrapper.title || "未命名项目"),
    description: String(wrapper.description || ""),
    source: (wrapper.source as WebCanvasWorkflow["source"]) || "blank",
    settings: {
      model: String(isRecord(old.settings) ? old.settings.model || "omni-水果 Pro" : "omni-水果 Pro"),
      ratio: String(isRecord(old.settings) ? old.settings.ratio || "1:1" : "1:1"),
      duration: String(isRecord(old.settings) ? old.settings.duration || "0s" : "0s"),
      resolution: String(isRecord(old.settings) ? old.settings.resolution || "2K" : "2K"),
    },
    viewport: {
      x: Number(viewport.x || 0),
      y: Number(viewport.y || 0),
      zoom: Number(viewport.zoom || viewport.scale || 1),
    },
    nodes: (Array.isArray(old.nodes) ? old.nodes : []).map((n: unknown) => isRecord(n) ? { ...n, position: isRecord(n.position) ? { ...n.position } : { x: 0, y: 0 } } : n) as WebCanvasWorkflowNode[],
    edges: Array.isArray(old.edges) ? old.edges : [],
    packages: Array.isArray(old.packages) ? old.packages : [],
  };
}

function cloneWorkflow(workflow: WebCanvasWorkflow): WebCanvasWorkflow {
  return {
    ...workflow,
    settings: { ...workflow.settings },
    nodes: workflow.nodes.map((node) => ({ ...node, position: { ...node.position } })),
    edges: workflow.edges.map((edge) => ({ ...edge })),
  };
}

function normalizeProjectContent(payload: unknown, projectId: string): WebCanvasWorkflow {
  const unwrapped = unwrapApiPayload(payload);
  const content = isRecord(unwrapped) && "content" in unwrapped ? unwrapped.content : unwrapped;

  // New format: content.workflowData is a full WebCanvasWorkflow
  if (isRecord(content) && isCanvasWorkflow(content.workflowData)) {
    return cloneWorkflow({ ...content.workflowData, id: content.workflowData.id || projectId });
  }

  // New format: content.workflow is a full WebCanvasWorkflow
  if (isRecord(content) && isCanvasWorkflow(content.workflow)) {
    return cloneWorkflow({ ...content.workflow, id: content.workflow.id || projectId });
  }

  // Content itself is a WebCanvasWorkflow
  if (isCanvasWorkflow(content)) {
    return cloneWorkflow({ ...content, id: content.id || projectId });
  }

  // Legacy format: wrapper has name/description, workflowData has nodes/edges/viewport
  if (isRecord(content) && isLegacyWorkflowData(content.workflowData)) {
    return migrateLegacyWorkflowData(content.workflowData, content, projectId);
  }

  throw new Error("Project content did not include a canvas workflow");
}

function normalizeLoginResult(payload: unknown): WebUserSession | null {
  const unwrapped = unwrapApiPayload(payload);
  if (!isRecord(unwrapped) || typeof unwrapped.token !== "string") return null;

  const user = normalizeUser(unwrapped.user ?? unwrapped);
  if (!user) return null;

  return {
    token: unwrapped.token,
    user,
    source: "server",
  };
}

function updateStoredSessionUser(user: WebUserSession["user"]): WebUserSession | null {
  const stored = readStoredSession();
  if (!stored) return null;

  const session: WebUserSession = {
    ...stored,
    user: {
      ...stored.user,
      ...user,
    },
  };
  writeStoredSession(session);
  return session;
}

function normalizeUsageSummary(payload: unknown): WebUsageSummary {
  const unwrapped = unwrapApiPayload(payload);
  const raw = isRecord(unwrapped) ? unwrapped : {};

  return {
    balanceCents: toNumber(
      raw.balanceCents ?? raw.balance_cents ?? raw.currentBalanceCents ?? raw.current_balance_cents,
    ),
    enterpriseBalanceCents:
      toOptionalNumber(
        raw.enterpriseBalanceCents ??
          raw.enterprise_balance_cents ??
          raw.enterpriseBalance ??
          raw.enterprise_balance,
      ) ?? undefined,
    imageUsed: toNumber(raw.imageUsed ?? raw.image_used ?? raw.imageCount ?? raw.image_count),
    videoUsed: toNumber(raw.videoUsed ?? raw.video_used ?? raw.videoCount ?? raw.video_count),
    textUsed: toNumber(
      raw.textUsed ?? raw.text_used ?? raw.textTokens ?? raw.text_tokens ?? raw.total_prompt_tokens,
    ),
    source: "server",
  };
}

function toOptionalNumber(value: unknown): number | null {
  if (value === undefined || value === null || value === "") return null;
  const numberValue = typeof value === "number" ? value : Number(value);
  return Number.isFinite(numberValue) ? numberValue : null;
}

function normalizeEnterpriseUsageSummary(payload: unknown): WebEnterpriseUsageSummary {
  const unwrapped = unwrapApiPayload(payload);
  const raw = isRecord(unwrapped) ? unwrapped : {};
  const memberRows = Array.isArray(raw.members) ? raw.members.filter(isRecord) : [];
  const modelRows = Array.isArray(raw.modelBreakdown)
    ? raw.modelBreakdown.filter(isRecord)
    : Array.isArray(raw.model_breakdown)
      ? raw.model_breakdown.filter(isRecord)
      : [];
  const recordRows = Array.isArray(raw.records)
    ? raw.records.filter(isRecord)
    : Array.isArray(raw.items)
      ? raw.items.filter(isRecord)
      : [];
  const trendRows = Array.isArray(raw.dailyTrend)
    ? raw.dailyTrend.filter(isRecord)
    : Array.isArray(raw.daily_trend)
      ? raw.daily_trend.filter(isRecord)
      : [];

  return {
    enterpriseId: toStringValue(raw.enterpriseId ?? raw.enterprise_id),
    enterpriseName: toStringValue(raw.enterpriseName ?? raw.enterprise_name, "Enterprise"),
    balanceCents: toNumber(raw.balanceCents ?? raw.balance_cents ?? raw.enterpriseBalanceCents ?? raw.enterprise_balance_cents),
    totalUsedCents: toNumber(raw.totalUsedCents ?? raw.total_used_cents ?? raw.usedCents ?? raw.used_cents),
    members: memberRows.map((member, index) => ({
      userId: toIdValue(member.userId ?? member.user_id ?? member.id, `member-${index + 1}`),
      username: toStringValue(member.username ?? member.userName ?? member.user_name ?? member.name, "employee"),
      displayName: toNullableString(member.displayName ?? member.display_name ?? member.nickname ?? member.name),
      role: toStringValue(member.role ?? member.enterpriseRole ?? member.enterprise_role, "employee"),
      usedCents: toNumber(
        member.usedCents ??
          member.used_cents ??
          member.amountCents ??
          member.amount_cents ??
          member.totalUsedCents ??
          member.total_used_cents,
      ),
      taskCount: toNumber(member.taskCount ?? member.task_count ?? member.calls ?? member.count),
      lastUsedAt: toNullableString(member.lastUsedAt ?? member.last_used_at ?? member.updatedAt ?? member.updated_at),
    })),
    modelBreakdown: modelRows.map((model) => ({
      model: toStringValue(model.model ?? model.modelId ?? model.model_id, "unknown"),
      usedCents: toNumber(model.usedCents ?? model.used_cents ?? model.amountCents ?? model.amount_cents),
      taskCount: toNumber(model.taskCount ?? model.task_count ?? model.calls ?? model.count),
    })),
    dailyTrend: trendRows.map((row) => ({
      date: toStringValue(row.date ?? row.day),
      usedCents: toNumber(row.usedCents ?? row.used_cents ?? row.amountCents ?? row.amount_cents),
      taskCount: toNumber(row.taskCount ?? row.task_count ?? row.count),
    })),
    records: recordRows.map((record, index) => ({
      id: toStringValue(record.id ?? record.ledgerId ?? record.ledger_id ?? record.taskId ?? record.task_id, `record-${index + 1}`),
      userId: toIdValue(record.userId ?? record.user_id ?? record.memberId ?? record.member_id, "unknown"),
      username: toStringValue(record.username ?? record.userName ?? record.user_name ?? record.name, "employee"),
      model: toStringValue(record.model ?? record.modelId ?? record.model_id, "unknown"),
      taskType: toStringValue(record.taskType ?? record.task_type ?? record.type, "image"),
      resolution: toNullableString(record.resolution ?? record.quality),
      durationSeconds: toOptionalNumber(record.durationSeconds ?? record.duration_seconds ?? record.duration),
      amountCents: toNumber(record.amountCents ?? record.amount_cents ?? record.usedCents ?? record.used_cents),
      prompt: toNullableString(record.prompt),
      status: toStringValue(record.status, "completed"),
      createdAt: toStringValue(record.createdAt ?? record.created_at ?? record.updatedAt ?? record.updated_at),
    })),
    source: "server",
  };
}

function normalizeRechargeOrder(payload: unknown): RechargeOrderResult {
  const raw = unwrapApiPayload(payload);
  if (!isRecord(raw)) {
    return { orderId: `local-${Date.now()}`, status: "pending", message: "订单已提交，请联系客服确认到账。" };
  }

  return {
    orderId: toStringValue(raw.orderId ?? raw.order_id ?? raw.id, `local-${Date.now()}`),
    status: toStringValue(raw.status, "pending"),
    payUrl: toNullableString(raw.payUrl ?? raw.pay_url ?? raw.checkoutUrl ?? raw.checkout_url),
    qrCodeUrl: toNullableString(raw.qrCodeUrl ?? raw.qr_code_url ?? raw.qrcodeUrl),
    message: toNullableString(raw.message ?? raw.notice),
  };
}

function buildProjectUpsertPayload(workflow: WebCanvasWorkflow, session: WebUserSession): Record<string, unknown> {
  const userId = String(session.user.id).replace(/[^a-zA-Z0-9_-]/g, "");
  const projectId = workflow.id.trim();
  const ossKey = `users/${userId}/projects/${projectId}/current/project.json`;

  return {
    id: projectId,
    name: workflow.title.trim() || "未命名项目",
    description: workflow.description.trim() || null,
    ossKey,
    thumbnailUrl: workflow.nodes.find((node) => node.previewUrl)?.previewUrl || null,
    storyboardCount: workflow.nodes.length,
    imageCount: workflow.nodes.filter((node) => node.kind === "image").length,
    videoCount: workflow.nodes.filter((node) => node.kind === "video").length,
    fileSize: JSON.stringify(workflow).length,
    fingerprint: createWorkflowFingerprint(workflow),
    deviceId: "web",
    baseRevision: null,
    forceOverwrite: true,
    saveReason: "create",
  };
}

export const keyServerClient = {
  getBaseUrl,
  getStoredSession: readStoredSession,
  updateStoredSessionUser,
  clearSession() {
    writeStoredSession(null);
  },
  async login(input: LoginInput): Promise<WebUserSession> {
    const session = normalizeLoginResult(
      await request<unknown>("/auth/login", {
        method: "POST",
        body: {
          username: input.username.trim(),
          password: input.password,
        },
      }),
    );
    if (!session) {
      throw new Error("Login response did not include a token and user");
    }

    writeStoredSession(session);
    return session;
  },
  async register(input: RegisterInput): Promise<WebUserSession> {
    const session = normalizeLoginResult(
      await request<unknown>("/auth/register", {
        method: "POST",
        body: {
          username: input.username.trim(),
          password: input.password,
          betaCode: input.betaCode.trim(),
        },
      }),
    );
    if (!session) {
      throw new Error("Register response did not include a token and user");
    }

    writeStoredSession(session);
    return session;
  },
  async loginEmail(input: EmailAuthInput): Promise<WebUserSession> {
    const session = normalizeLoginResult(
      await request<unknown>("/auth/login-email", {
        method: "POST",
        body: {
          email: input.email.trim(),
          password: input.password,
        },
      }),
    );
    if (!session) {
      throw new Error("Email login response did not include a token and user");
    }

    writeStoredSession(session);
    return session;
  },
  async registerEmail(input: EmailAuthInput): Promise<WebUserSession> {
    const session = normalizeLoginResult(
      await request<unknown>("/auth/register-email", {
        method: "POST",
        body: {
          email: input.email.trim(),
          username: input.username?.trim() || undefined,
          password: input.password,
          code: input.code?.trim() || undefined,
          betaCode: input.betaCode?.trim() || undefined,
        },
      }),
    );
    if (!session) {
      throw new Error("Email register response did not include a token and user");
    }

    writeStoredSession(session);
    return session;
  },
  async sendSmsCode(phone: string, purpose: "login" | "register", betaCode?: string): Promise<{ cooldownSeconds?: number; ttlSeconds?: number }> {
    return request<{ cooldownSeconds?: number; ttlSeconds?: number }>("/auth/sms/send", {
      method: "POST",
      body: { phone: phone.trim(), purpose, betaCode: betaCode?.trim() || undefined },
    });
  },
  async sendEmailCode(email: string, purpose: "login" | "register" | "reset", betaCode?: string): Promise<{ cooldownSeconds?: number; ttlSeconds?: number; devCode?: string }> {
    return request<{ cooldownSeconds?: number; ttlSeconds?: number; devCode?: string }>("/auth/email/send-code", {
      method: "POST",
      body: { email: email.trim(), purpose, betaCode: betaCode?.trim() || undefined },
    });
  },
  async verifyEmail(input: EmailCodeInput): Promise<{ success: boolean }> {
    return request<{ success: boolean }>("/auth/email/verify", {
      method: "POST",
      body: { email: input.email.trim(), code: input.code.trim(), purpose: input.purpose || "register" },
    });
  },
  async forgotPassword(input: ForgotPasswordInput): Promise<{ success: boolean; message?: string }> {
    return request<{ success: boolean; message?: string }>("/auth/forgot-password", {
      method: "POST",
      body: { email: input.email.trim() },
    });
  },
  async resetPassword(input: ResetPasswordInput): Promise<{ success: boolean; message?: string }> {
    return request<{ success: boolean; message?: string }>("/auth/reset-password", {
      method: "POST",
      body: { email: input.email.trim(), code: input.code.trim(), newPassword: input.newPassword },
    });
  },
  async loginPhone(input: PhoneAuthInput): Promise<WebUserSession> {
    const session = normalizeLoginResult(
      await request<unknown>("/auth/login-phone", {
        method: "POST",
        body: {
          phone: input.phone.trim(),
          code: input.code.trim(),
        },
      }),
    );
    if (!session) {
      throw new Error("Phone login response did not include a token and user");
    }

    writeStoredSession(session);
    return session;
  },
  async registerPhone(input: PhoneAuthInput): Promise<WebUserSession> {
    const session = normalizeLoginResult(
      await request<unknown>("/auth/register-phone", {
        method: "POST",
        body: {
          phone: input.phone.trim(),
          code: input.code.trim(),
          password: input.password || "",
          betaCode: input.betaCode?.trim() || undefined,
        },
      }),
    );
    if (!session) {
      throw new Error("Phone register response did not include a token and user");
    }

    writeStoredSession(session);
    return session;
  },
  async getWechatLoginTicket(): Promise<WechatLoginTicket> {
    const browserCrypto = typeof globalThis !== "undefined" ? globalThis.crypto : undefined;
    const state =
      browserCrypto && "randomUUID" in browserCrypto
        ? browserCrypto.randomUUID().replace(/-/g, "")
        : `${Date.now().toString(36)}${Math.random().toString(36).slice(2)}`;
    return request<WechatLoginTicket>(`/auth/wechat/login-url?state=${encodeURIComponent(state)}`);
  },
  async getWechatLoginSession(state: string): Promise<WechatLoginSessionStatus> {
    const response = await request<unknown>(`/auth/wechat/session?state=${encodeURIComponent(state)}`);
    const raw = isRecord(response) ? response : {};
    const session = normalizeLoginResult(raw);
    if (session) {
      writeStoredSession(session);
      return { status: "completed", session };
    }

    const status = toStringValue(raw.status, "pending");
    return {
      status,
      error: toNullableString(raw.error) ?? undefined,
    };
  },
  async updateProfile(input: UpdateProfileInput): Promise<WebUserSession["user"]> {
    const user = normalizeUser(
      await request<unknown>("/auth/profile", {
        method: "PUT",
        body: {
          ...input,
          profileBackgroundUrl: input.profileBackgroundUrl ?? input.backgroundUrl ?? undefined,
        },
      }),
    );
    if (!user) {
      throw new Error("Profile response did not include a user");
    }

    updateStoredSessionUser(user);
    return user;
  },
  async getCurrentSession(): Promise<WebUserSession | null> {
    const stored = readStoredSession();
    if (!stored) {
      return null;
    }

    try {
      const user = normalizeUser(await request<unknown>("/auth/me", { token: stored.token }));
      if (!user) {
        throw new Error("Current-user response did not include a user");
      }

      const session: WebUserSession = { ...stored, user, source: "server", errorMessage: undefined };
      writeStoredSession(session);
      return session;
    } catch (error) {
      if (isHttpError(error) && (error.status === 401 || error.status === 403)) {
        writeStoredSession(null);
        return null;
      }
      return {
        ...stored,
        source: "server",
        errorMessage: getErrorMessage(error),
      };
    }
  },
  async listProjects(): Promise<WebProjectSummary[]> {
    const summaries = extractProjectRows(await request<unknown>("/projects")).map((project) =>
      toProjectSummary(project, "server"),
    );
    return enrichProjectSummariesWithContent(summaries);
  },
  async getProjectContent(projectId: string): Promise<WebCanvasWorkflow> {
    const stored = readStoredSession();
    if (!stored) {
      throw new Error("需要先登录");
    }

    const safeProjectId = encodeURIComponent(projectId.trim());
    if (!safeProjectId) {
      throw new Error("Project id is required");
    }

    const response = await request<unknown>(`/projects/${safeProjectId}/content?resolveMedia=1`);
    return normalizeProjectContent(response, projectId);
  },
  async getUsageSummary(): Promise<WebUsageSummary> {
    const stored = readStoredSession();
    return normalizeUsageSummary(await request<unknown>("/user/usage/summary", { token: stored?.token }));
  },
  async getEnterpriseUsageSummary(): Promise<WebEnterpriseUsageSummary> {
    const stored = readStoredSession();
    return normalizeEnterpriseUsageSummary(await request<unknown>("/enterprise/usage/summary", { token: stored?.token }));
  },
  async getPersonalUsageSummary(): Promise<WebEnterpriseUsageSummary> {
    const stored = readStoredSession();
    return normalizeEnterpriseUsageSummary(await request<unknown>("/user/usage/credits", { token: stored?.token }));
  },
  async createRechargeOrder(input: RechargeOrderInput): Promise<RechargeOrderResult> {
    const response = await request<unknown>("/payments/recharge-orders", {
      method: "POST",
      body: input,
    });
    return normalizeRechargeOrder(response);
  },
  async createProjectSpace(workflow: WebCanvasWorkflow): Promise<WebProjectSummary> {
    const stored = readStoredSession();
    if (!stored) {
      const error = new Error("需要先登录");
      throw error;
    }

    const payload = buildProjectUpsertPayload(workflow, stored);
    const response = await request<unknown>("/projects/upsert", {
      method: "POST",
      body: payload,
    });
    const projectPayload = isRecord(response) ? response.project ?? response : response;
    if (!isRecord(projectPayload)) {
      throw new Error("Project response did not include a project");
    }

    return toProjectSummary(projectPayload, "server");
  },
  async saveProjectContent(projectId: string, workflow: WebCanvasWorkflow): Promise<WebProjectSummary> {
    const stored = readStoredSession();
    if (!stored) {
      throw new Error("需要先登录");
    }

    const response = await request<unknown>(`projects/${encodeURIComponent(projectId)}/content`, {
      method: "PUT",
      body: {
        content: {
          name: workflow.title,
          description: workflow.description,
          workflowData: workflow,
          nodes: workflow.nodes,
          edges: workflow.edges,
        },
        meta: {
          name: workflow.title,
          description: workflow.description,
          thumbnailUrl: workflow.nodes.find((node) => node.previewUrl)?.previewUrl || null,
          storyboardCount: workflow.nodes.length,
          imageCount: workflow.nodes.filter((node) => node.kind === "image").length,
          videoCount: workflow.nodes.filter((node) => node.kind === "video").length,
        },
        saveReason: "web-create",
        deviceId: "web",
        forceOverwrite: true,
      },
    });
    const rawProject = isRecord(response) && isRecord(response.project) ? response.project : response;
    if (!isRecord(rawProject)) {
      return buildProjectSummaryFromWorkflow(workflow, "server");
    }
    return toProjectSummary(rawProject, "server");
  },
  async deleteProject(projectId: string, options?: DeleteProjectOptions): Promise<void> {
    const stored = readStoredSession();
    if (!stored) {
      throw new Error("需要先登录");
    }

    const path = options?.cleanupUserData ? `projects/${encodeURIComponent(projectId)}?cleanupUserData=1` : `projects/${encodeURIComponent(projectId)}`;
    await request(path, {
      method: "DELETE",
    });
  },

  async getClientErrors(page = 1): Promise<{ items: import("../components/AdminMonitor").ClientErrorItem[]; total: number }> {
    const data = await request<{ items: import("../components/AdminMonitor").ClientErrorItem[]; total: number }>(`/client-errors?page=${page}`);
    return data;
  },
};
