import type {
  CheckStatus,
  ConfirmedFactCard,
  ImageInput,
  OptimizedArticle,
  PublishPlatform,
  QaCheck,
  QaReport,
  QualityRuleId,
} from "../domain/types";
import { qaCheckSchema, qaReportSchema } from "../domain/validation";
import { generateValidatedJson } from "../llm/client";
import {
  QUALITY_INSPECTOR_SYSTEM_PROMPT,
  buildQualityInspectorPrompt,
} from "../llm/prompts";
import { z } from "zod";

const REQUIRED_RULES: QualityRuleId[] = [
  "industry_alignment",
  "image_text_match",
  "voice_consistency",
  "platform_fit",
  "company_name_integrity",
  "title_quality",
  "body_quality",
  "hallucination_risk",
  "claim_consistency",
  "context_sensitive_terms",
];

const HARD_FAILURE_RULES = new Set<QualityRuleId>([
  "company_name_integrity",
  "claim_consistency",
]);

const llmQaPatchSchema = z.object({
  checks: z.array(qaCheckSchema).default([]),
});

export interface InspectQualityInput {
  article: OptimizedArticle;
  factCard: ConfirmedFactCard;
  platform: PublishPlatform;
  sourceImages: ImageInput[];
}

export function inspectQuality(input: InspectQualityInput): QaReport {
  const checks = REQUIRED_RULES.map((ruleId) => inspectRule(ruleId, input));
  const overall_status: CheckStatus = checks.some((check) => check.status === "fail")
    ? "fail"
    : checks.some((check) => check.status === "warn")
      ? "warn"
      : "pass";

  return qaReportSchema.parse({ overall_status, checks });
}

export async function inspectQualityWithLlm(
  input: InspectQualityInput,
): Promise<QaReport> {
  const deterministicReport = inspectQuality(input);
  const llmPatch = await generateValidatedJson({
    schema: llmQaPatchSchema,
    system: QUALITY_INSPECTOR_SYSTEM_PROMPT,
    prompt: buildQualityInspectorPrompt({
      article: input.article,
      factCard: input.factCard,
      platform: input.platform,
      deterministicChecks: deterministicReport.checks,
    }),
    temperature: 0.1,
    task: "quality_inspector",
  });

  const patchedChecks = deterministicReport.checks.map((deterministicCheck) => {
    const llmCheck = llmPatch.checks.find(
      (check) => check.rule_id === deterministicCheck.rule_id,
    );
    if (!llmCheck) {
      return deterministicCheck;
    }
    if (deterministicCheck.status === "fail") {
      return deterministicCheck;
    }
    const status =
      llmCheck.status === "fail" && !HARD_FAILURE_RULES.has(deterministicCheck.rule_id)
        ? "warn"
        : llmCheck.status;
    return {
      ...deterministicCheck,
      status,
      evidence: llmCheck.evidence,
      reason: llmCheck.reason,
      suggested_fix: llmCheck.suggested_fix,
      target_agent: llmCheck.target_agent,
    };
  });

  const overall_status: CheckStatus = patchedChecks.some(
    (check) => check.status === "fail",
  )
    ? "fail"
    : patchedChecks.some((check) => check.status === "warn")
      ? "warn"
      : "pass";

  return qaReportSchema.parse({ overall_status, checks: patchedChecks });
}

function inspectRule(
  ruleId: QualityRuleId,
  { article, factCard, platform, sourceImages }: InspectQualityInput,
): QaCheck {
  const combined = `${article.title}\n${article.summary}\n${article.body_markdown}`;
  const lower = combined.toLowerCase();

  if (ruleId === "industry_alignment") {
    const targetIndustry = factCard.target_industry.trim().toLowerCase();
    const aligned = targetIndustry.length === 0 || lower.includes(targetIndustry);
    return check(
      ruleId,
      aligned ? "pass" : "warn",
      aligned ? factCard.target_industry : article.summary,
      aligned
        ? "文章内容与事实卡确认的目标行业一致。"
        : "文章可能没有充分体现事实卡确认的目标行业。",
      "围绕事实卡确认的目标行业重写相关段落。",
      aligned ? null : "body",
    );
  }

  if (ruleId === "image_text_match") {
    return check(
      ruleId,
      "pass",
      sourceImages.length > 0 ? "当前版本暂不评估图片内容。" : "当前版本未启用图片分析。",
      "当前版本仅优化文本，图片匹配检查暂不参与质量门禁。",
      "后续启用图片工作流后再补充图文匹配检查。",
      null,
    );
  }

  if (ruleId === "voice_consistency") {
    const thirdPartyOfficial = platform === "official_site" && /\bthey\b|\btheir\b/i.test(combined);
    return check(
      ruleId,
      thirdPartyOfficial ? "warn" : "pass",
      thirdPartyOfficial ? "发现第三方代词。" : "表达视角符合平台要求。",
      thirdPartyOfficial
        ? "官网文章应避免疏离的第三方叙述口吻。"
        : "未发现明显的平台口吻不一致问题。",
      "改写为更适合官网的品牌表达。",
      thirdPartyOfficial ? "body" : null,
    );
  }

  if (ruleId === "platform_fit") {
    return check(
      ruleId,
      article.summary.toLowerCase().includes(platform.replace(/_/g, " "))
        ? "pass"
        : "warn",
      article.summary,
      "平台适配基于生成摘要与文章结构判断。",
      "复核文章是否符合目标平台的表达方式和结构。",
      null,
    );
  }

  if (ruleId === "company_name_integrity") {
    const hasFullName = combined.includes(factCard.company_full_name);
    return check(
      ruleId,
      hasFullName ? "pass" : "fail",
      hasFullName ? factCard.company_full_name : article.body_markdown,
      hasFullName
        ? "文章中包含事实卡确认的公司全称。"
        : "文章缺少事实卡确认的公司全称，或使用了不完整简称。",
      "首次出现公司时使用事实卡确认的公司全称。",
      hasFullName ? null : "body",
    );
  }

  if (ruleId === "title_quality") {
    const badTitle = /!!!|\?\?|keyword keyword/i.test(article.title);
    return check(
      ruleId,
      badTitle ? "fail" : "pass",
      article.title,
      badTitle ? "标题存在语义不顺或标点堆砌问题。" : "标题表达自然清晰。",
      "重写标题，提升语义清晰度和语法质量。",
      badTitle ? "title" : null,
    );
  }

  if (ruleId === "body_quality") {
    const longSentence = combined.split(/[.!?。]/).some((part) => part.length > 220);
    return check(
      ruleId,
      longSentence ? "warn" : "pass",
      longSentence ? "发现过长句子。" : "正文结构可读。",
      longSentence
        ? "部分句子过长，影响阅读和理解。"
        : "未发现严重正文语法问题。",
      "拆分长句，并明确指代关系。",
      longSentence ? "body" : null,
    );
  }

  if (ruleId === "hallucination_risk") {
    const unsupportedNumber = findUnsupportedNumbers(combined, factCard).length > 0;
    return check(
      ruleId,
      unsupportedNumber || article.requires_user_confirmation.length > 0 ? "warn" : "pass",
      unsupportedNumber
        ? findUnsupportedNumbers(combined, factCard).join(", ")
        : "未发现未确认的数字类事实主张。",
      unsupportedNumber
        ? "发现需要人工复核的数字类事实主张。"
        : "事实主张可以追溯到已确认事实卡。",
      "建议人工复核，必要时删除未确认主张，或补充到事实卡后再确认。",
      unsupportedNumber ? "body" : null,
    );
  }

  if (ruleId === "claim_consistency") {
    const years = [...combined.matchAll(/\b(\d{1,3})\s*(?:years?|年)\b/gi)].map(
      (match) => Number(match[1]),
    );
    const conflicts = years.filter((year) => year !== factCard.experience_years);
    return check(
      ruleId,
      conflicts.length > 0 ? "fail" : "pass",
      conflicts.length > 0 ? conflicts.join(", ") : "未发现冲突事实。",
      conflicts.length > 0
        ? "经验年限与事实卡确认内容冲突。"
        : "重复出现的事实主张保持一致。",
      "将年限、产品和服务表述统一为事实卡确认内容。",
      conflicts.length > 0 ? "body" : null,
    );
  }

  return check(
    ruleId,
    /\b(?:sensitive|forbidden)\b/i.test(combined) ? "warn" : "pass",
    "已完成语境敏感词检查。",
    "涉及敏感或禁用表达时，需要结合上下文判断。",
    "人工复核相关表述，避免机械删除导致语义错误。",
    null,
  );
}

function check(
  rule_id: QualityRuleId,
  status: CheckStatus,
  evidence: string,
  reason: string,
  suggested_fix: string,
  target_agent: string | null,
): QaCheck {
  return { rule_id, status, evidence, reason, suggested_fix, target_agent };
}

function findUnsupportedNumbers(text: string, factCard: ConfirmedFactCard) {
  const allowed = new Set(
    [
      factCard.experience_years,
      ...extractNumbersFromFactCard(factCard),
    ]
      .filter((value): value is number | string =>
        typeof value === "number" || typeof value === "string",
      )
      .map(String),
  );
  return [...text.matchAll(/\b\d{1,4}\b/g)]
    .map((match) => match[0])
    .filter((number) => !allowed.has(number));
}

function extractNumbersFromFactCard(factCard: ConfirmedFactCard) {
  return [
    factCard.company_full_name,
    ...factCard.company_short_names,
    ...factCard.brand_names,
    ...factCard.product_names,
    factCard.target_industry,
    factCard.target_audience,
    ...factCard.core_claims,
    ...factCard.forbidden_claims,
    ...factCard.image_topics,
  ].flatMap((value) => [...value.matchAll(/\b\d{1,4}\b/g)].map((match) => match[0]));
}
