import { useEffect, useState, type FormEvent } from 'react';
import { Select as AntSelect } from 'antd';
import { Gauge, Pencil, Plus, RotateCcw, Route, Save, ShieldCheck, Trash2 } from 'lucide-react';
import type { GatewayRunnerPolicy, GatewayRunnerPolicyUpsertRequest, RuntimePolicySet, RuntimePolicySetUpsertRequest } from '@easyai-ai-gateway/contracts';
import { Badge, Button, Card, CardContent, CardHeader, CardTitle, ConfirmDialog, FormDialog, Input, Label, Select, Tabs, Textarea } from '../../components/ui';
import type { LoadState } from '../../types';

type RuntimePanelTab = 'model' | 'runner';
type RunnerPolicyStrategy = 'failover' | 'hardStop' | 'priorityDemote';

type RuntimePolicyForm = {
  policyKey: string;
  name: string;
  description: string;
  rpm: string;
  tpm: string;
  concurrency: string;
  retryEnabled: boolean;
  retryMaxAttempts: string;
  retryAllowKeywords: string[];
  retryDenyKeywords: string[];
  autoDisableEnabled: boolean;
  autoDisableThreshold: string;
  autoDisableKeywords: string[];
  degradeEnabled: boolean;
  degradeCooldownSeconds: string;
  degradeKeywords: string[];
  metadataJson: string;
  status: string;
};

type RunnerPolicyForm = {
  name: string;
  description: string;
  failoverEnabled: boolean;
  maxPlatforms: string;
  maxDurationSeconds: string;
  allowCategories: string[];
  denyCategories: string[];
  allowCodes: string[];
  denyCodes: string[];
  allowKeywords: string[];
  denyKeywords: string[];
  allowStatusCodes: string[];
  denyStatusCodes: string[];
  failoverActions: Record<string, unknown>;
  hardStopEnabled: boolean;
  hardStopCategories: string[];
  hardStopCodes: string[];
  hardStopStatusCodes: string[];
  hardStopKeywords: string[];
  priorityDemoteEnabled: boolean;
  priorityDemoteCategories: string[];
  priorityDemoteCodes: string[];
  priorityDemoteStatusCodes: string[];
  priorityDemoteKeywords: string[];
  metadataJson: string;
  status: string;
};

const failoverActionDefinitions = [
  {
    value: 'next',
    title: '仅轮转',
    description: '这些错误分类只尝试下一个平台，不修改当前平台状态。',
  },
  {
    value: 'disable_and_next',
    title: '禁用后轮转',
    description: '这些错误分类会先禁用当前平台，再尝试下一个平台。',
  },
  {
    value: 'cooldown_and_next',
    title: '冷却后轮转',
    description: '这些错误分类会先让当前平台模型进入冷却，再尝试下一个平台。',
  },
] as const;

const failoverActionValues = new Set<string>(failoverActionDefinitions.map((item) => item.value));

const failoverCategoryOptions = [
  'network',
  'timeout',
  'stream_error',
  'rate_limit',
  'provider_5xx',
  'provider_overloaded',
  'auth_error',
  'request_error',
  'unsupported_model',
  'user_permission',
  'insufficient_balance',
  'client_error',
].map((item) => ({ label: item, value: item }));

export function RuntimePoliciesPanel(props: {
  message: string;
  runnerPolicy: GatewayRunnerPolicy | null;
  runtimePolicySets: RuntimePolicySet[];
  state: LoadState;
  onDeleteRuntimePolicySet: (policySetId: string) => Promise<void>;
  onSaveRunnerPolicy: (input: GatewayRunnerPolicyUpsertRequest) => Promise<void>;
  onSaveRuntimePolicySet: (input: RuntimePolicySetUpsertRequest, policySetId?: string) => Promise<void>;
}) {
  const [activeTab, setActiveTab] = useState<RuntimePanelTab>('model');
  const [dialogOpen, setDialogOpen] = useState(false);
  const [editingId, setEditingId] = useState('');
  const [form, setForm] = useState<RuntimePolicyForm>(() => createDefaultForm());
  const [runnerForm, setRunnerForm] = useState<RunnerPolicyForm>(() => runnerPolicyToForm(null));
  const [localError, setLocalError] = useState('');
  const [pendingDeletePolicy, setPendingDeletePolicy] = useState<RuntimePolicySet | null>(null);

  useEffect(() => {
    setRunnerForm(runnerPolicyToForm(props.runnerPolicy));
  }, [props.runnerPolicy?.id, props.runnerPolicy?.updatedAt]);

  function openCreateDialog() {
    setEditingId('');
    setLocalError('');
    setForm(createDefaultForm(`runtime-${Date.now().toString(36)}`));
    setDialogOpen(true);
  }

  function editPolicy(policy: RuntimePolicySet) {
    setEditingId(policy.id);
    setLocalError('');
    setForm(policyToForm(policy));
    setDialogOpen(true);
  }

  function closeDialog() {
    setDialogOpen(false);
    setEditingId('');
    setLocalError('');
    setForm(createDefaultForm());
  }

  async function submit(event: FormEvent<HTMLFormElement>) {
    event.preventDefault();
    setLocalError('');
    try {
      await props.onSaveRuntimePolicySet(formToPayload(form), editingId || undefined);
      closeDialog();
    } catch (err) {
      setLocalError(err instanceof Error ? err.message : '运行策略保存失败');
    }
  }

  async function deletePolicy(policy: RuntimePolicySet) {
    if (isDefaultPolicy(policy)) return;
    try {
      await props.onDeleteRuntimePolicySet(policy.id);
      setPendingDeletePolicy(null);
    } catch (err) {
      setLocalError(err instanceof Error ? err.message : '运行策略删除失败');
    }
  }

  async function submitRunnerPolicy(event: FormEvent<HTMLFormElement>) {
    event.preventDefault();
    setLocalError('');
    try {
      await props.onSaveRunnerPolicy(runnerFormToPayload(runnerForm));
    } catch (err) {
      setLocalError(err instanceof Error ? err.message : '全局调度策略保存失败');
    }
  }

  return (
    <div className="pageStack">
      <Card>
        <CardHeader>
          <div>
            <CardTitle>运行策略</CardTitle>
            <p className="mutedText">模型运行策略维护限流、平台内重试、自动禁用和降级；全局调度策略控制平台间切换、硬拒绝和失败平台优先级降级。</p>
          </div>
          {activeTab === 'model' && <Button type="button" onClick={openCreateDialog}>
            <Plus size={15} />
            新增策略
          </Button>}
        </CardHeader>
        <CardContent>
          {(props.message || localError) && <p className="formMessage">{localError || props.message}</p>}
          <Tabs
            value={activeTab}
            tabs={[
              { value: 'model', label: '模型运行策略', icon: <ShieldCheck size={15} /> },
              { value: 'runner', label: '全局调度策略', icon: <Route size={15} /> },
            ]}
            onValueChange={setActiveTab}
          />
        </CardContent>
      </Card>

      {activeTab === 'runner' && (
        <RunnerPolicyEditor
          form={runnerForm}
          loading={props.state === 'loading'}
          onChange={setRunnerForm}
          onSubmit={submitRunnerPolicy}
        />
      )}

      {activeTab === 'model' && (
        <section className="runtimePolicyGrid">
          {props.runtimePolicySets.map((policy) => (
            <article className="runtimePolicyCard" key={policy.id}>
              <header>
                <div className="iconBox"><ShieldCheck size={18} /></div>
                <div>
                  <strong>{policy.name}</strong>
                  <span>{policy.policyKey}</span>
                </div>
                <Badge variant={policy.status === 'active' ? 'success' : 'secondary'}>{policy.status}</Badge>
              </header>
              {policy.description && <p>{policy.description}</p>}
              <div className="runtimePolicySummary">
                <span><Gauge size={13} />{rateLimitSummary(policy)}</span>
                <span>{retrySummary(policy)}</span>
                <span>{autoDisableSummary(policy)}</span>
                <span>{degradeSummary(policy)}</span>
              </div>
              <footer>
                <Button type="button" variant="outline" size="sm" onClick={() => editPolicy(policy)}>
                  <Pencil size={14} />
                  修改
                </Button>
                <Button
                  type="button"
                  variant="destructive"
                  size="sm"
                  disabled={isDefaultPolicy(policy)}
                  title={isDefaultPolicy(policy) ? '默认运行策略不能删除' : undefined}
                  onClick={() => setPendingDeletePolicy(policy)}
                >
                  <Trash2 size={14} />
                  删除
                </Button>
              </footer>
            </article>
          ))}
        </section>
      )}

      <FormDialog
        bodyClassName="runtimePolicyFormBody"
        className="runtimePolicyDialog"
        eyebrow={editingId ? 'Edit Runtime Policy' : 'New Runtime Policy'}
        footer={(
          <>
            <Button type="button" variant="outline" onClick={closeDialog}>
              <RotateCcw size={15} />
              取消
            </Button>
            <Button type="submit" disabled={props.state === 'loading'}>
              {editingId ? <Pencil size={15} /> : <Plus size={15} />}
              {editingId ? '保存修改' : '新增策略'}
            </Button>
          </>
        )}
        open={dialogOpen}
        title={editingId ? '编辑运行策略' : '新增运行策略'}
        onClose={closeDialog}
        onSubmit={submit}
      >
        <Label>策略 Key<Input value={form.policyKey} onChange={(event) => setForm({ ...form, policyKey: event.target.value })} /></Label>
        <Label>名称<Input value={form.name} onChange={(event) => setForm({ ...form, name: event.target.value })} /></Label>
        <Label className="spanTwo">描述<Input value={form.description} onChange={(event) => setForm({ ...form, description: event.target.value })} /></Label>

        <section className="runtimePolicySection spanTwo">
          <header><strong>限流策略</strong><span>TPM / RPM / 并发</span></header>
          <div className="runtimePolicyRows">
            <Label>RPM / 分钟请求<Input value={form.rpm} inputMode="numeric" onChange={(event) => setForm({ ...form, rpm: event.target.value })} /></Label>
            <Label>TPM / 分钟 Token<Input value={form.tpm} inputMode="numeric" onChange={(event) => setForm({ ...form, tpm: event.target.value })} /></Label>
            <Label>并发请求<Input value={form.concurrency} inputMode="numeric" onChange={(event) => setForm({ ...form, concurrency: event.target.value })} /></Label>
          </div>
        </section>

        <section className="runtimePolicySection spanTwo">
          <header><strong>平台内重试策略</strong><span>允许/拒绝关键词控制同一平台是否再次调用</span></header>
          <div className="runtimePolicyRows">
            <Toggle checked={form.retryEnabled} label="允许同平台重试" onChange={(checked) => setForm({ ...form, retryEnabled: checked })} />
            <Label>最大尝试次数<Input value={form.retryMaxAttempts} inputMode="numeric" onChange={(event) => setForm({ ...form, retryMaxAttempts: event.target.value })} /></Label>
            <KeywordField label="允许重试关键词" value={form.retryAllowKeywords} onChange={(value) => setForm({ ...form, retryAllowKeywords: value })} />
            <KeywordField label="拒绝重试关键词" value={form.retryDenyKeywords} onChange={(value) => setForm({ ...form, retryDenyKeywords: value })} />
          </div>
        </section>

        <section className="runtimePolicySection spanTwo">
          <header><strong>禁用与降级</strong><span>自动禁用错误关键词、优先级降级关键词</span></header>
          <div className="runtimePolicyRows">
            <Toggle checked={form.autoDisableEnabled} label="启用自动禁用" onChange={(checked) => setForm({ ...form, autoDisableEnabled: checked })} />
            <Label>禁用触发次数<Input value={form.autoDisableThreshold} inputMode="numeric" onChange={(event) => setForm({ ...form, autoDisableThreshold: event.target.value })} /></Label>
            <KeywordField label="自动禁用关键词" value={form.autoDisableKeywords} onChange={(value) => setForm({ ...form, autoDisableKeywords: value })} />
            <Toggle checked={form.degradeEnabled} label="启用优先级降级" onChange={(checked) => setForm({ ...form, degradeEnabled: checked })} />
            <Label>降级冷却秒数<Input value={form.degradeCooldownSeconds} inputMode="numeric" onChange={(event) => setForm({ ...form, degradeCooldownSeconds: event.target.value })} /></Label>
            <KeywordField label="降级关键词" value={form.degradeKeywords} onChange={(value) => setForm({ ...form, degradeKeywords: value })} />
          </div>
        </section>

        <Label>状态<Input value={form.status} onChange={(event) => setForm({ ...form, status: event.target.value })} /></Label>
        <Label className="spanTwo">元数据 JSON<Textarea value={form.metadataJson} rows={4} onChange={(event) => setForm({ ...form, metadataJson: event.target.value })} /></Label>
      </FormDialog>
      <ConfirmDialog
        confirmLabel="删除策略"
        description="已绑定模型会清空策略绑定，删除后不可恢复。"
        loading={props.state === 'loading'}
        open={Boolean(pendingDeletePolicy)}
        title={`确认删除运行策略 ${pendingDeletePolicy?.name ?? ''}？`}
        onCancel={() => setPendingDeletePolicy(null)}
        onConfirm={() => pendingDeletePolicy ? deletePolicy(pendingDeletePolicy) : undefined}
      />
    </div>
  );
}

function Toggle(props: { checked: boolean; label: string; onChange: (checked: boolean) => void }) {
  return (
    <label className="platformToggle">
      <input type="checkbox" checked={props.checked} onChange={(event) => props.onChange(event.target.checked)} />
      <span><strong>{props.label}</strong></span>
    </label>
  );
}

function RunnerPolicyEditor(props: {
  form: RunnerPolicyForm;
  loading: boolean;
  onChange: (form: RunnerPolicyForm) => void;
  onSubmit: (event: FormEvent<HTMLFormElement>) => void;
}) {
  const [activeStrategy, setActiveStrategy] = useState<RunnerPolicyStrategy>('failover');
  const patch = (next: Partial<RunnerPolicyForm>) => props.onChange({ ...props.form, ...next });
  const strategyDefinitions = [
    {
      value: 'failover' as const,
      title: '平台间故障切换',
      description: '当前平台内部重试耗尽后是否尝试下一个候选平台',
      enabled: props.form.failoverEnabled,
      icon: <Route size={15} />,
    },
    {
      value: 'hardStop' as const,
      title: '硬拒绝规则',
      description: '参数、余额、权限等错误直接失败，不受模型覆盖影响',
      enabled: props.form.hardStopEnabled,
      icon: <ShieldCheck size={15} />,
    },
    {
      value: 'priorityDemote' as const,
      title: '优先级降级',
      description: '命中后将失败平台的动态优先级调整到当前模型队尾',
      enabled: props.form.priorityDemoteEnabled,
      icon: <Gauge size={15} />,
    },
  ];
  const activeDefinition = strategyDefinitions.find((item) => item.value === activeStrategy) ?? strategyDefinitions[0];

  return (
    <Card>
      <CardHeader className="runnerPolicyHeader">
        <div className="runnerPolicyHeaderText">
          <CardTitle>{props.form.name}</CardTitle>
          <p className="mutedText">{props.form.description}</p>
        </div>
        <Label className="runnerPolicyHeaderStatus">
          状态
          <Select value={props.form.status} onChange={(event) => patch({ status: event.target.value })}>
            <option value="active">启用</option>
            <option value="disabled">停用</option>
          </Select>
        </Label>
      </CardHeader>
      <CardContent>
        <form className="runtimePolicyFormBody runnerPolicyForm" onSubmit={props.onSubmit}>
          <div className="capabilityWorkbench runnerPolicyWorkbench spanTwo">
            <aside className="capabilitySidebar">
              <div className="capabilitySidebarTitle">
                <Route size={15} />
                <strong>策略列表</strong>
              </div>
              <div className="capabilityList">
                {strategyDefinitions.map((item) => (
                  <button className="capabilityListItem" data-active={item.value === activeStrategy} key={item.value} type="button" onClick={() => setActiveStrategy(item.value)}>
                    <span>
                      <strong>{item.title}</strong>
                      <small>{item.description}</small>
                    </span>
                    <Badge variant={item.enabled ? 'success' : 'secondary'}>{item.enabled ? '启用' : '关闭'}</Badge>
                  </button>
                ))}
              </div>
            </aside>

            <div className="capabilityFormPanel runnerPolicyDetailPanel">
              <header>
                {activeDefinition.icon}
                <div>
                  <strong>{activeDefinition.title}</strong>
                  <small>{activeDefinition.description}</small>
                </div>
                <span>{activeDefinition.enabled ? '启用' : '关闭'}</span>
              </header>

              {activeStrategy === 'failover' && (
                <div className="runtimePolicyRows runnerPolicyDetailRows">
                  <Toggle checked={props.form.failoverEnabled} label="启用平台间切换" onChange={(checked) => patch({ failoverEnabled: checked })} />
                  <Label>
                    最大平台数
                    <Input value={props.form.maxPlatforms} inputMode="numeric" onChange={(event) => patch({ maxPlatforms: event.target.value })} />
                    <span className="runtimeFieldHint">本次任务最多尝试的候选平台数量，默认 99。</span>
                  </Label>
                  <Label>
                    最大时间（秒）
                    <Input value={props.form.maxDurationSeconds} inputMode="numeric" onChange={(event) => patch({ maxDurationSeconds: event.target.value })} />
                    <span className="runtimeFieldHint">从任务开始执行计时，超过后不再继续重试，默认 600 秒。</span>
                  </Label>
                  <FailoverCategoryRoutingEditor
                    actions={props.form.failoverActions}
                    allowCategories={props.form.allowCategories}
                    denyCategories={props.form.denyCategories}
                    onChange={(value) => patch(value)}
                  />
                  <div className="spanTwo runnerSupplementalRules">
                    <strong>补充触发条件（可自定义扩展）</strong>
                    <small>这些条件只决定是否进入平台间切换；分类命中后的处理方式以上面的分组为准。</small>
                  </div>
                  <KeywordField label="补充触发错误码" value={props.form.allowCodes} onChange={(value) => patch({ allowCodes: value })} />
                  <KeywordField label="排除触发错误码" value={props.form.denyCodes} onChange={(value) => patch({ denyCodes: value })} />
                  <KeywordField label="补充触发关键词" value={props.form.allowKeywords} onChange={(value) => patch({ allowKeywords: value })} />
                  <KeywordField label="排除触发关键词" value={props.form.denyKeywords} onChange={(value) => patch({ denyKeywords: value })} />
                  <KeywordField label="补充触发状态码" value={props.form.allowStatusCodes} onChange={(value) => patch({ allowStatusCodes: value })} />
                  <KeywordField label="排除触发状态码" value={props.form.denyStatusCodes} onChange={(value) => patch({ denyStatusCodes: value })} />
                </div>
              )}

              {activeStrategy === 'hardStop' && (
                <div className="runtimePolicyRows runnerPolicyDetailRows">
                  <Toggle checked={props.form.hardStopEnabled} label="启用硬拒绝" onChange={(checked) => patch({ hardStopEnabled: checked })} />
                  <KeywordField label="硬拒绝分类" value={props.form.hardStopCategories} onChange={(value) => patch({ hardStopCategories: value })} />
                  <KeywordField label="硬拒绝错误码" value={props.form.hardStopCodes} onChange={(value) => patch({ hardStopCodes: value })} />
                  <KeywordField label="硬拒绝状态码" value={props.form.hardStopStatusCodes} onChange={(value) => patch({ hardStopStatusCodes: value })} />
                  <span className="runtimeFieldHint spanTwo">建议只用错误码、分类和关键词做硬拒绝；401/403 这类平台鉴权错误默认走平台间切换。</span>
                  <KeywordField label="硬拒绝关键词" value={props.form.hardStopKeywords} onChange={(value) => patch({ hardStopKeywords: value })} />
                </div>
              )}

              {activeStrategy === 'priorityDemote' && (
                <div className="runtimePolicyRows runnerPolicyDetailRows">
                  <Toggle checked={props.form.priorityDemoteEnabled} label="启用优先级降级" onChange={(checked) => patch({ priorityDemoteEnabled: checked })} />
                  <span className="runtimeFieldHint spanTwo">命中降级规则后，失败平台会自动调整到提供当前模型的其它客户端优先级队尾。</span>
                  <KeywordField label="降级分类" value={props.form.priorityDemoteCategories} onChange={(value) => patch({ priorityDemoteCategories: value })} />
                  <KeywordField label="降级错误码" value={props.form.priorityDemoteCodes} onChange={(value) => patch({ priorityDemoteCodes: value })} />
                  <KeywordField label="降级状态码" value={props.form.priorityDemoteStatusCodes} onChange={(value) => patch({ priorityDemoteStatusCodes: value })} />
                  <KeywordField label="降级关键词" value={props.form.priorityDemoteKeywords} onChange={(value) => patch({ priorityDemoteKeywords: value })} />
                </div>
              )}
            </div>
          </div>

          <Label className="spanTwo">元数据 JSON<Textarea value={props.form.metadataJson} rows={4} onChange={(event) => patch({ metadataJson: event.target.value })} /></Label>
          <div className="runtimePolicyActions spanTwo">
            <Button type="submit" disabled={props.loading}>
              <Save size={15} />
              保存全局调度策略
            </Button>
          </div>
        </form>
      </CardContent>
    </Card>
  );
}

function KeywordField(props: { label: string; value: string[]; onChange: (value: string[]) => void }) {
  const options = props.value.map((item) => ({ label: item, value: item }));
  return (
    <Label className="spanTwo runtimeTagField">
      {props.label}
      <AntSelect
        allowClear
        className="runtimeTagInput"
        maxTagCount="responsive"
        mode="tags"
        options={options}
        placeholder="输入后回车生成标签"
        tokenSeparators={[',', '\n']}
        value={props.value}
        onChange={(value) => props.onChange(cleanTags(value))}
      />
    </Label>
  );
}

function FailoverCategoryRoutingEditor(props: {
  actions: Record<string, unknown>;
  allowCategories: string[];
  denyCategories: string[];
  onChange: (value: Pick<RunnerPolicyForm, 'allowCategories' | 'denyCategories' | 'failoverActions'>) => void;
}) {
  const groups = failoverCategoryRoutingGroups(props.allowCategories, props.denyCategories, props.actions);
  const options = categoryOptions(props.allowCategories, props.denyCategories, Object.keys(props.actions));
  const updateGroup = (group: string, value: string[]) => {
    props.onChange(updateFailoverCategoryRouting(props.allowCategories, props.denyCategories, props.actions, group, value));
  };
  return (
    <div className="spanTwo runnerActionMatrix">
      <div className="runnerActionIntro">
        <strong>错误分类处理方式（一般保持默认无需修改）</strong>
        <small>每个错误分类只放在一个分组；前三组会进入平台间切换，拒绝轮转不会尝试后续平台。</small>
      </div>
      <div className="runnerActionGrid">
        {failoverActionDefinitions.map((action) => (
          <label className="runnerActionGroup" key={action.value} data-action={action.value}>
            <span>
              <strong>{action.title}</strong>
              <small>{action.description}</small>
            </span>
            <AntSelect
              allowClear
              className="runtimeTagInput"
              maxTagCount="responsive"
              mode="tags"
              options={options}
              placeholder="输入错误分类后回车"
              tokenSeparators={[',', '\n']}
              value={groups[action.value] ?? []}
              onChange={(value) => updateGroup(action.value, value)}
            />
          </label>
        ))}
        <label className="runnerActionGroup" data-action="deny">
          <span>
            <strong>拒绝轮转</strong>
            <small>这些错误分类不会尝试后续平台，当前任务直接失败。</small>
          </span>
          <AntSelect
            allowClear
            className="runtimeTagInput"
            maxTagCount="responsive"
            mode="tags"
            options={options}
            placeholder="输入错误分类后回车"
            tokenSeparators={[',', '\n']}
            value={groups.deny ?? []}
            onChange={(value) => updateGroup('deny', value)}
          />
        </label>
      </div>
    </div>
  );
}

function runnerPolicyToForm(policy: GatewayRunnerPolicy | null): RunnerPolicyForm {
  const failover = readObject(policy?.failoverPolicy);
  const hardStop = readObject(policy?.hardStopPolicy);
  const priorityDemote = readObject(policy?.priorityDemotePolicy);
  return {
    name: policy?.name ?? '默认全局调度策略',
    description: policy?.description ?? '控制多个候选平台之间的故障切换；模型运行策略只可覆盖 failoverPolicy，不能覆盖 hardStopPolicy。',
    failoverEnabled: readBool(failover.enabled, true),
    maxPlatforms: String(readNumber(failover.maxPlatforms, 99)),
    maxDurationSeconds: String(readNumber(failover.maxDurationSeconds, 600)),
    allowCategories: tagsFromValue(failover.allowCategories ?? ['network', 'timeout', 'stream_error', 'rate_limit', 'provider_5xx', 'provider_overloaded', 'auth_error']),
    denyCategories: tagsFromValue(failover.denyCategories ?? ['request_error', 'unsupported_model', 'user_permission', 'insufficient_balance']),
    allowCodes: tagsFromValue(failover.allowCodes ?? ['auth_failed', 'invalid_api_key', 'missing_credentials']),
    denyCodes: tagsFromValue(failover.denyCodes ?? []),
    allowKeywords: tagsFromValue(failover.allowKeywords ?? ['timeout', 'network', 'rate_limit', 'overloaded', 'temporarily_unavailable', 'server_error', 'auth_failed', 'invalid_api_key', 'missing_credentials', 'unauthorized', 'forbidden', '429', '5xx']),
    denyKeywords: tagsFromValue(failover.denyKeywords ?? ['invalid_parameter', 'missing required', 'bad request']),
    allowStatusCodes: tagsFromValue(failover.allowStatusCodes ?? [401, 403, 408, 429, 500, 502, 503, 504]),
    denyStatusCodes: tagsFromValue(failover.denyStatusCodes ?? []),
    failoverActions: Object.keys(readObject(failover.actions)).length > 0 ? readObject(failover.actions) : defaultFailoverActions(),
    hardStopEnabled: readBool(hardStop.enabled, true),
    hardStopCategories: tagsFromValue(hardStop.categories ?? ['request_error', 'unsupported_model', 'user_permission', 'insufficient_balance']),
    hardStopCodes: tagsFromValue(hardStop.codes ?? ['bad_request', 'invalid_request', 'invalid_parameter', 'missing_required', 'unsupported_kind', 'unsupported_model', 'insufficient_balance', 'permission_denied']),
    hardStopStatusCodes: tagsFromValue(hardStop.statusCodes ?? []),
    hardStopKeywords: tagsFromValue(hardStop.keywords ?? ['invalid_parameter', 'missing required', 'bad request', 'insufficient balance']),
    priorityDemoteEnabled: readBool(priorityDemote.enabled, true),
    priorityDemoteCategories: tagsFromValue(priorityDemote.categories ?? ['network', 'timeout', 'stream_error', 'rate_limit', 'provider_5xx', 'provider_overloaded']),
    priorityDemoteCodes: tagsFromValue(priorityDemote.codes ?? ['network', 'timeout', 'stream_read_error', 'rate_limit', 'server_error', 'overloaded']),
    priorityDemoteStatusCodes: tagsFromValue(priorityDemote.statusCodes ?? [408, 429, 500, 502, 503, 504]),
    priorityDemoteKeywords: tagsFromValue(priorityDemote.keywords ?? ['timeout', 'network', 'rate_limit', 'overloaded', 'temporarily_unavailable', 'server_error', '429', '5xx']),
    metadataJson: JSON.stringify(policy?.metadata ?? {}, null, 2),
    status: policy?.status ?? 'active',
  };
}

function runnerFormToPayload(form: RunnerPolicyForm): GatewayRunnerPolicyUpsertRequest {
  return {
    policyKey: 'default-runner-v1',
    name: form.name.trim() || '默认全局调度策略',
    description: form.description.trim() || undefined,
    failoverPolicy: {
      enabled: form.failoverEnabled,
      maxPlatforms: positiveInt(form.maxPlatforms, 99),
      maxDurationSeconds: positiveInt(form.maxDurationSeconds, 600),
      allowCategories: cleanTags(form.allowCategories),
      denyCategories: cleanTags(form.denyCategories),
      allowCodes: cleanTags(form.allowCodes),
      denyCodes: cleanTags(form.denyCodes),
      allowKeywords: cleanTags(form.allowKeywords),
      denyKeywords: cleanTags(form.denyKeywords),
      allowStatusCodes: parseNumberTags(form.allowStatusCodes),
      denyStatusCodes: parseNumberTags(form.denyStatusCodes),
      actions: normalizedFailoverActions(form),
    },
    hardStopPolicy: {
      enabled: form.hardStopEnabled,
      categories: cleanTags(form.hardStopCategories),
      codes: cleanTags(form.hardStopCodes),
      statusCodes: parseNumberTags(form.hardStopStatusCodes),
      keywords: cleanTags(form.hardStopKeywords),
    },
    priorityDemotePolicy: {
      enabled: form.priorityDemoteEnabled,
      categories: cleanTags(form.priorityDemoteCategories),
      codes: cleanTags(form.priorityDemoteCodes),
      statusCodes: parseNumberTags(form.priorityDemoteStatusCodes),
      keywords: cleanTags(form.priorityDemoteKeywords),
    },
    metadata: parseJson(form.metadataJson),
    status: form.status.trim() || 'active',
  };
}

function createDefaultForm(policyKey = 'default-runtime-v1'): RuntimePolicyForm {
  return {
    policyKey,
    name: policyKey === 'default-runtime-v1' ? '默认运行策略' : '',
    description: '',
    rpm: '120',
    tpm: '240000',
    concurrency: '6',
    retryEnabled: true,
    retryMaxAttempts: '2',
    retryAllowKeywords: ['rate_limit', 'timeout', 'server_error', 'network', '429', '5xx'],
    retryDenyKeywords: ['invalid_api_key', 'insufficient_quota', 'billing_not_active', 'permission_denied'],
    autoDisableEnabled: false,
    autoDisableThreshold: '3',
    autoDisableKeywords: ['invalid_api_key', 'account_deactivated', 'permission_denied', 'billing_not_active'],
    degradeEnabled: true,
    degradeCooldownSeconds: '300',
    degradeKeywords: ['rate_limit', 'quota', 'timeout', 'temporarily_unavailable', 'overloaded'],
    metadataJson: '{}',
    status: 'active',
  };
}

function defaultFailoverActions(): Record<string, unknown> {
  return {
    auth_error: 'disable_and_next',
    rate_limit: 'cooldown_and_next',
    provider_5xx: 'next',
    request_error: 'stop',
  };
}

function failoverCategoryRoutingGroups(allowCategories: string[], denyCategories: string[], actions: Record<string, unknown>) {
  const assignments = new Map<string, string>();
  for (const category of cleanTags(allowCategories)) {
    const rawAction = actions[category];
    const action = typeof rawAction === 'string' ? rawAction : '';
    assignments.set(category, failoverActionValues.has(action) ? action : 'next');
  }
  for (const [category, rawAction] of Object.entries(actions)) {
    if (assignments.has(category)) continue;
    const action = typeof rawAction === 'string' ? rawAction : '';
    if (action === 'stop') {
      assignments.set(category, 'deny');
    } else if (failoverActionValues.has(action)) {
      assignments.set(category, action);
    }
  }
  for (const category of cleanTags(denyCategories)) {
    assignments.set(category, 'deny');
  }
  const groups: Record<string, string[]> = {};
  for (const [category, group] of assignments.entries()) {
    groups[group] = [...(groups[group] ?? []), category];
  }
  for (const key of [...failoverActionDefinitions.map((item) => item.value), 'deny']) {
    groups[key] = cleanTags(groups[key] ?? []);
  }
  return groups;
}

function updateFailoverCategoryRouting(
  allowCategories: string[],
  denyCategories: string[],
  actions: Record<string, unknown>,
  group: string,
  value: string[],
): Pick<RunnerPolicyForm, 'allowCategories' | 'denyCategories' | 'failoverActions'> {
  const groups = failoverCategoryRoutingGroups(allowCategories, denyCategories, actions);
  groups[group] = cleanTags(value);
  const nextActions: Record<string, unknown> = {};
  const knownCategories = new Set<string>();
  const nextAllowCategories: string[] = [];
  for (const action of failoverActionDefinitions) {
    for (const category of cleanTags(groups[action.value] ?? [])) {
      knownCategories.add(category);
      nextAllowCategories.push(category);
      if (action.value !== 'next') {
        nextActions[category] = action.value;
      }
    }
  }
  const nextDenyCategories = cleanTags(groups.deny ?? []);
  for (const category of nextDenyCategories) {
    knownCategories.add(category);
  }
  for (const [category, rawAction] of Object.entries(actions)) {
    const shouldPreserveCustomAction = (
      !knownCategories.has(category)
      && typeof rawAction === 'string'
      && rawAction !== 'next'
      && rawAction !== 'stop'
      && !failoverActionValues.has(rawAction)
    );
    if (!shouldPreserveCustomAction) continue;
    // Keep forward-compatible custom actions that this editor cannot render.
    nextActions[category] = rawAction;
  }
  return {
    allowCategories: cleanTags(nextAllowCategories),
    denyCategories: nextDenyCategories,
    failoverActions: nextActions,
  };
}

function normalizedFailoverActions(form: RunnerPolicyForm) {
  const groups = failoverCategoryRoutingGroups(form.allowCategories, form.denyCategories, form.failoverActions);
  const nextActions: Record<string, unknown> = {};
  for (const action of failoverActionDefinitions) {
    if (action.value === 'next') continue;
    for (const category of cleanTags(groups[action.value] ?? [])) {
      nextActions[category] = action.value;
    }
  }
  return nextActions;
}

function categoryOptions(...values: string[][]) {
  const knownValues = new Set(failoverCategoryOptions.map((option) => option.value));
  const options = [...failoverCategoryOptions];
  for (const value of values.flat()) {
    const tag = String(value).trim();
    if (!tag || knownValues.has(tag)) continue;
    knownValues.add(tag);
    options.push({ label: tag, value: tag });
  }
  return options;
}

function policyToForm(policy: RuntimePolicySet): RuntimePolicyForm {
  const rateRules = Array.isArray(policy.rateLimitPolicy?.rules) ? policy.rateLimitPolicy.rules : [];
  const retry = readObject(policy.retryPolicy);
  const disable = readObject(policy.autoDisablePolicy);
  const degrade = readObject(policy.degradePolicy);
  return {
    policyKey: policy.policyKey,
    name: policy.name,
    description: policy.description ?? '',
    rpm: String(readRateLimit(rateRules, 'rpm') || ''),
    tpm: String(readRateLimit(rateRules, 'tpm_total') || ''),
    concurrency: String(readRateLimit(rateRules, 'concurrent') || ''),
    retryEnabled: readBool(retry.enabled, true),
    retryMaxAttempts: String(readNumber(retry.maxAttempts, 2)),
    retryAllowKeywords: tagsFromValue(retry.allowKeywords),
    retryDenyKeywords: tagsFromValue(retry.denyKeywords),
    autoDisableEnabled: readBool(disable.enabled, false),
    autoDisableThreshold: String(readNumber(disable.threshold, 3)),
    autoDisableKeywords: tagsFromValue(disable.keywords),
    degradeEnabled: readBool(degrade.enabled, true),
    degradeCooldownSeconds: String(readNumber(degrade.cooldownSeconds, 300)),
    degradeKeywords: tagsFromValue(degrade.keywords),
    metadataJson: JSON.stringify(policy.metadata ?? {}, null, 2),
    status: policy.status || 'active',
  };
}

function formToPayload(form: RuntimePolicyForm): RuntimePolicySetUpsertRequest {
  return {
    policyKey: form.policyKey.trim(),
    name: form.name.trim(),
    description: form.description.trim() || undefined,
    rateLimitPolicy: { rules: rateLimitRules(form) },
    retryPolicy: {
      enabled: form.retryEnabled,
      maxAttempts: positiveInt(form.retryMaxAttempts, 2),
      allowKeywords: cleanTags(form.retryAllowKeywords),
      denyKeywords: cleanTags(form.retryDenyKeywords),
    },
    autoDisablePolicy: {
      enabled: form.autoDisableEnabled,
      threshold: positiveInt(form.autoDisableThreshold, 3),
      keywords: cleanTags(form.autoDisableKeywords),
    },
    degradePolicy: {
      enabled: form.degradeEnabled,
      cooldownSeconds: positiveInt(form.degradeCooldownSeconds, 300),
      keywords: cleanTags(form.degradeKeywords),
    },
    metadata: parseJson(form.metadataJson),
    status: form.status.trim() || 'active',
  };
}

function rateLimitRules(form: RuntimePolicyForm) {
  return [
    limitRule('rpm', form.rpm),
    limitRule('tpm_total', form.tpm),
    limitRule('concurrent', form.concurrency, 60, 120),
  ].filter((rule): rule is NonNullable<ReturnType<typeof limitRule>> => Boolean(rule));
}

function limitRule(metric: string, value: string, windowSeconds = 60, leaseTtlSeconds?: number) {
  const limit = Number(value);
  if (!Number.isFinite(limit) || limit <= 0) return undefined;
  return { metric, limit, windowSeconds, leaseTtlSeconds };
}

function isDefaultPolicy(policy: RuntimePolicySet) {
  return policy.policyKey === 'default-runtime-v1';
}

function rateLimitSummary(policy: RuntimePolicySet) {
  const rules = Array.isArray(policy.rateLimitPolicy?.rules) ? policy.rateLimitPolicy.rules : [];
  const rpm = readRateLimit(rules, 'rpm') || '-';
  const tpm = readRateLimit(rules, 'tpm_total') || '-';
  const concurrent = readRateLimit(rules, 'concurrent') || '-';
  return `RPM ${rpm} / TPM ${tpm} / 并发 ${concurrent}`;
}

function retrySummary(policy: RuntimePolicySet) {
  const retry = readObject(policy.retryPolicy);
  return readBool(retry.enabled, false) ? `同平台尝试 ${readNumber(retry.maxAttempts, 2)} 次` : '同平台不重试';
}

function autoDisableSummary(policy: RuntimePolicySet) {
  const disable = readObject(policy.autoDisablePolicy);
  return readBool(disable.enabled, false) ? `自动禁用: ${stringifyKeywords(disable.keywords) || '-'}` : '自动禁用关闭';
}

function degradeSummary(policy: RuntimePolicySet) {
  const degrade = readObject(policy.degradePolicy);
  return readBool(degrade.enabled, false) ? `降级: ${stringifyKeywords(degrade.keywords) || '-'}` : '降级关闭';
}

function readRateLimit(rules: unknown[], metric: string) {
  const rule = rules.find((item) => readObject(item).metric === metric);
  return readNumber(readObject(rule).limit, 0);
}

function stringifyKeywords(value: unknown) {
  return tagsFromValue(value).join(', ');
}

function tagsFromValue(value: unknown) {
  if (Array.isArray(value)) return cleanTags(value.map((item) => String(item)));
  return typeof value === 'string' ? cleanTags([value]) : [];
}

function cleanTags(value: string[]) {
  const tags: string[] = [];
  const seen = new Set<string>();
  for (const raw of value) {
    for (const item of raw.split(/[,\n]/)) {
      const tag = item.trim();
      if (!tag || seen.has(tag)) continue;
      seen.add(tag);
      tags.push(tag);
    }
  }
  return tags;
}

function parseNumberTags(value: string[]) {
  return cleanTags(value).map((item) => Number.parseInt(item, 10)).filter((item) => Number.isFinite(item) && item > 0);
}

function positiveInt(value: string, fallback: number) {
  const parsed = Number.parseInt(value, 10);
  return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
}

function readNumber(value: unknown, fallback: number) {
  const parsed = Number(value);
  return Number.isFinite(parsed) ? parsed : fallback;
}

function readBool(value: unknown, fallback: boolean) {
  return typeof value === 'boolean' ? value : fallback;
}

function readObject(value: unknown) {
  return value && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : {};
}

function parseJson(value: string) {
  const parsed = value.trim() ? JSON.parse(value) : {};
  if (!parsed || Array.isArray(parsed) || typeof parsed !== 'object') {
    throw new Error('元数据 JSON 必须是对象');
  }
  return parsed as Record<string, unknown>;
}
