import { z } from "zod";

export const RULE_CAPS = { maxDepth: 8, maxNodes: 64, maxBytes: 8192 } as const;

export const CHANCE_SOURCE_OPTIONS: {
  value: API.LotteryChanceSourceType;
  labelKey: string;
  labelDefault: string;
}[] = [
  {
    value: "daily_signin",
    labelKey: "rules.source.dailySignin",
    labelDefault: "Daily Sign-in",
  },
  {
    value: "new_subscription",
    labelKey: "rules.source.newSubscription",
    labelDefault: "New Subscription",
  },
  {
    value: "invite_success",
    labelKey: "rules.source.inviteSuccess",
    labelDefault: "Invite Success",
  },
  {
    value: "manual_grant",
    labelKey: "rules.source.manualGrant",
    labelDefault: "Manual Grant",
  },
];

export const CONDITION_TYPES = [
  "has_subscription",
  "subscription_type",
  "invite_count",
  "total_recharge",
  "register_days",
  "user_tag",
] as const;

export type ConditionType = (typeof CONDITION_TYPES)[number];

export interface ConditionRow {
  type: ConditionType;
  minDaysRemaining?: number;
  planIds?: string;
  min?: number;
  windowDays?: number;
  minUsdt?: number;
  tags?: string;
}

export interface ConditionParamField {
  key: keyof Omit<ConditionRow, "type">;
  inputType: "number" | "text";
  labelKey: string;
  labelDefault: string;
  placeholderKey?: string;
  placeholderDefault?: string;
}

export const CONDITION_PARAM_FIELDS: Record<
  ConditionType,
  ConditionParamField[]
> = {
  has_subscription: [
    {
      key: "minDaysRemaining",
      inputType: "number",
      labelKey: "rules.param.minDaysRemaining",
      labelDefault: "Min Days Remaining",
    },
  ],
  subscription_type: [
    {
      key: "planIds",
      inputType: "text",
      labelKey: "rules.param.planIds",
      labelDefault: "Plan IDs",
      placeholderKey: "rules.param.planIdsPlaceholder",
      placeholderDefault: "Comma separated, e.g. 1,2,3",
    },
  ],
  invite_count: [
    {
      key: "min",
      inputType: "number",
      labelKey: "rules.param.min",
      labelDefault: "Min Count",
    },
    {
      key: "windowDays",
      inputType: "number",
      labelKey: "rules.param.windowDays",
      labelDefault: "Window Days (optional)",
    },
  ],
  total_recharge: [
    {
      key: "minUsdt",
      inputType: "number",
      labelKey: "rules.param.minUsdt",
      labelDefault: "Min USDT",
    },
  ],
  register_days: [
    {
      key: "min",
      inputType: "number",
      labelKey: "rules.param.min",
      labelDefault: "Min Count",
    },
  ],
  user_tag: [
    {
      key: "tags",
      inputType: "text",
      labelKey: "rules.param.tags",
      labelDefault: "Tags",
      placeholderKey: "rules.param.tagsPlaceholder",
      placeholderDefault: "Comma separated, e.g. vip,beta",
    },
  ],
};

export function parseIntList(input?: string): number[] {
  if (!input) return [];
  return input
    .split(/[,，\s]+/)
    .map((item) => item.trim())
    .filter(Boolean)
    .map((item) => Number.parseInt(item, 10))
    .filter((item) => Number.isFinite(item));
}

export function parseStringList(input?: string): string[] {
  if (!input) return [];
  return input
    .split(/[,，]+/)
    .map((item) => item.trim())
    .filter(Boolean);
}

export function conditionToNode(row: ConditionRow): API.LotteryEligibilityNode {
  switch (row.type) {
    case "has_subscription":
      return {
        type: row.type,
        params: { min_days_remaining: row.minDaysRemaining ?? 0 },
      };
    case "subscription_type":
      return {
        type: row.type,
        params: { plan_ids: parseIntList(row.planIds) },
      };
    case "invite_count": {
      const params: Record<string, number> = { min: row.min ?? 0 };
      if (row.windowDays) params.window_days = row.windowDays;
      return { type: row.type, params };
    }
    case "total_recharge":
      return { type: row.type, params: { min_usdt: row.minUsdt ?? 0 } };
    case "register_days":
      return { type: row.type, params: { min: row.min ?? 0 } };
    case "user_tag":
      return { type: row.type, params: { tags: parseStringList(row.tags) } };
  }
}

export function buildEligibility(
  op: "AND" | "OR",
  rows: ConditionRow[]
): API.LotteryEligibilityNode | Record<string, never> {
  if (rows.length === 0) return {};
  return { op, children: rows.map(conditionToNode) };
}

function isLeafNode(node: API.LotteryEligibilityNode): boolean {
  return Boolean(node.type) && !node.op && !node.children?.length;
}

function nodeToCondition(
  node: API.LotteryEligibilityNode
): ConditionRow | null {
  const type = CONDITION_TYPES.find((item) => item === node.type);
  if (!type) return null;
  const params = node.params ?? {};
  switch (type) {
    case "has_subscription":
      return { type, minDaysRemaining: params.min_days_remaining };
    case "subscription_type":
      return {
        type,
        planIds: Array.isArray(params.plan_ids)
          ? params.plan_ids.join(",")
          : "",
      };
    case "invite_count":
      return { type, min: params.min, windowDays: params.window_days };
    case "total_recharge":
      return { type, minUsdt: params.min_usdt };
    case "register_days":
      return { type, min: params.min };
    case "user_tag":
      return {
        type,
        tags: Array.isArray(params.tags) ? params.tags.join(",") : "",
      };
  }
}

export function isEmptyEligibility(
  eligibility: API.LotteryActivity["eligibility"]
): boolean {
  return (
    eligibility === null ||
    eligibility === undefined ||
    Object.keys(eligibility).length === 0
  );
}

/**
 * 尝试把规则树还原成「一层 op + children 全是叶子」的简单模式；
 * 结构更复杂时返回 null（调用方应切到高级模式）。
 */
export function treeToSimple(
  eligibility: API.LotteryActivity["eligibility"]
): { op: "AND" | "OR"; rows: ConditionRow[] } | null {
  if (isEmptyEligibility(eligibility)) return { op: "AND", rows: [] };
  const tree = eligibility as API.LotteryEligibilityNode;
  if (
    (tree.op !== "AND" && tree.op !== "OR") ||
    !Array.isArray(tree.children) ||
    !tree.children.every(isLeafNode)
  ) {
    return null;
  }
  const rows: ConditionRow[] = [];
  for (const child of tree.children) {
    const row = nodeToCondition(child);
    if (!row) return null;
    rows.push(row);
  }
  return { op: tree.op, rows };
}

export interface RuleCapsResult {
  depth: number;
  nodes: number;
  bytes: number;
  depthExceeded: boolean;
  nodesExceeded: boolean;
  bytesExceeded: boolean;
  ok: boolean;
}

export function checkRuleCaps(
  tree: API.LotteryEligibilityNode | Record<string, never>
): RuleCapsResult {
  function walk(node: API.LotteryEligibilityNode): {
    depth: number;
    nodes: number;
  } {
    const children = Array.isArray(node.children) ? node.children : [];
    let maxChildDepth = 0;
    let nodes = 1;
    for (const child of children) {
      const result = walk(child);
      maxChildDepth = Math.max(maxChildDepth, result.depth);
      nodes += result.nodes;
    }
    return { depth: maxChildDepth + 1, nodes };
  }

  const { depth, nodes } = walk(tree);
  const bytes = new TextEncoder().encode(JSON.stringify(tree)).length;
  const depthExceeded = depth > RULE_CAPS.maxDepth;
  const nodesExceeded = nodes > RULE_CAPS.maxNodes;
  const bytesExceeded = bytes > RULE_CAPS.maxBytes;
  return {
    depth,
    nodes,
    bytes,
    depthExceeded,
    nodesExceeded,
    bytesExceeded,
    ok: !(depthExceeded || nodesExceeded || bytesExceeded),
  };
}

export type TranslateFn = (key: string, defaultValue: string) => string;

/** 规则树超限时返回具体超限项的提示文案，未超限返回 null。 */
export function buildCapsErrorMessage(
  t: TranslateFn,
  eligibility: API.LotteryEligibilityNode | Record<string, never>
): string | null {
  if (isEmptyEligibility(eligibility)) return null;
  const caps = checkRuleCaps(eligibility);
  if (caps.ok) return null;
  const fill = (template: string, value: number, limit: number) =>
    template
      .replace("{{value}}", String(value))
      .replace("{{limit}}", String(limit));
  const parts: string[] = [];
  if (caps.depthExceeded) {
    parts.push(
      fill(
        t("rules.capsDepthExceeded", "Depth {{value}} exceeds limit {{limit}}"),
        caps.depth,
        RULE_CAPS.maxDepth
      )
    );
  }
  if (caps.nodesExceeded) {
    parts.push(
      fill(
        t(
          "rules.capsNodesExceeded",
          "Node count {{value}} exceeds limit {{limit}}"
        ),
        caps.nodes,
        RULE_CAPS.maxNodes
      )
    );
  }
  if (caps.bytesExceeded) {
    parts.push(
      fill(
        t(
          "rules.capsBytesExceeded",
          "Size {{value}} bytes exceeds limit {{limit}} bytes"
        ),
        caps.bytes,
        RULE_CAPS.maxBytes
      )
    );
  }
  return parts.join("; ");
}

export function validateJsonTree(input: string, t: TranslateFn): string | null {
  try {
    const parsed: unknown = JSON.parse(input);
    if (parsed === null) return null;
    if (typeof parsed !== "object" || Array.isArray(parsed)) {
      return t("rules.jsonMustBeObject", "Rule tree must be a JSON object");
    }
    return null;
  } catch (error: unknown) {
    return error instanceof Error ? error.message : "Invalid JSON";
  }
}

const chanceSourceSchema = z.object({
  source: z.enum([
    "daily_signin",
    "new_subscription",
    "invite_success",
    "manual_grant",
  ]),
  amount: z.number().int().min(0),
  daily_limit: z.number().int().min(0).optional(),
});

const conditionSchema = z.object({
  type: z.enum(CONDITION_TYPES),
  minDaysRemaining: z.number().int().min(0).optional(),
  planIds: z.string().optional(),
  min: z.number().int().min(0).optional(),
  windowDays: z.number().int().min(0).optional(),
  minUsdt: z.number().min(0).optional(),
  tags: z.string().optional(),
});

export const getRuleEditorSchema = (t: TranslateFn) =>
  z
    .object({
      chanceSources: z.array(chanceSourceSchema),
      mode: z.enum(["simple", "advanced"]),
      op: z.enum(["AND", "OR"]),
      conditions: z.array(conditionSchema),
      advancedJson: z.string(),
    })
    .superRefine((data, ctx) => {
      const seen = new Set<string>();
      data.chanceSources.forEach((row, index) => {
        if (seen.has(row.source)) {
          ctx.addIssue({
            code: z.ZodIssueCode.custom,
            message: t("rules.duplicateSource", "Duplicate source"),
            path: ["chanceSources", index, "source"],
          });
        }
        seen.add(row.source);
      });
      if (data.mode === "advanced" && data.advancedJson.trim()) {
        const error = validateJsonTree(data.advancedJson, t);
        if (error) {
          ctx.addIssue({
            code: z.ZodIssueCode.custom,
            message: error,
            path: ["advancedJson"],
          });
        }
      }
    });

export type RuleEditorValues = z.infer<ReturnType<typeof getRuleEditorSchema>>;

export function toRuleEditorValues(
  activity: API.LotteryActivity
): RuleEditorValues {
  const chanceSources = (activity.chance_sources ?? []).map((row) => ({
    source: row.source,
    amount: row.amount,
    daily_limit: row.daily_limit ?? undefined,
  }));
  const simple = treeToSimple(activity.eligibility);
  if (simple) {
    return {
      chanceSources,
      mode: "simple",
      op: simple.op,
      conditions: simple.rows,
      advancedJson: "",
    };
  }
  return {
    chanceSources,
    mode: "advanced",
    op: "AND",
    conditions: [],
    advancedJson: JSON.stringify(activity.eligibility, null, 2),
  };
}

export function resolveEligibility(
  values: RuleEditorValues
): API.LotteryEligibilityNode | Record<string, never> {
  if (values.mode === "simple") {
    return buildEligibility(values.op, values.conditions);
  }
  const text = values.advancedJson.trim();
  if (!text) return {};
  const parsed = JSON.parse(text) as API.LotteryEligibilityNode | null;
  return parsed ?? {};
}
