"""审计异常标题，并可用全文中的标题安全矫正。"""
import csv
import os

from django.conf import settings
from django.core.management.base import BaseCommand, CommandError
from django.db.models.functions import Length
from django.utils import timezone

from apps.resm.models import Paper
from apps.resm.title_utils import (
    FulltextTitleCandidate,
    assess_title,
    clean_title,
    extract_title_from_pdf,
    extract_title_from_xml,
    is_better_title,
    is_usable_title,
)


def _paper_path(paper, ext):
    safe_doi = paper.doi.replace("/", "_")
    if paper.publication_date is None:
        directory = os.path.join(settings.BASE_DIR, "media", "papers", "unknown")
    else:
        d = paper.publication_date
        directory = os.path.join(
            settings.BASE_DIR, "media", "papers", str(d.year), str(d.month), str(d.day)
        )
    return os.path.join(directory, f"{safe_doi}.{ext}")


def _fulltext_candidate(paper):
    candidates = []
    xml_path = _paper_path(paper, "xml")
    pdf_path = _paper_path(paper, "pdf")
    # 历史状态位可能与磁盘不一致；审计以实际文件为准，避免“文件存在但 flag=false”漏检。
    if paper.has_fulltext_xml or os.path.isfile(xml_path):
        candidate = extract_title_from_xml(xml_path, paper.doi)
        if candidate:
            candidates.append(candidate)
    if paper.has_fulltext_pdf or os.path.isfile(pdf_path):
        candidate = extract_title_from_pdf(
            pdf_path, paper.doi, paper.first_author or ""
        )
        if candidate:
            candidates.append(candidate)
    rank = {"high": 3, "medium": 2, "low": 1}
    return max(candidates, key=lambda item: rank[item.confidence], default=None)


class Command(BaseCommand):
    help = "审计摘要化/网页化标题，并可用 fulltext XML/PDF 标题矫正"

    def add_arguments(self, parser):
        parser.add_argument("--doi", action="append", default=[], help="只处理指定 DOI，可重复")
        parser.add_argument("--min-length", type=int, default=300, help="初筛标题最小长度")
        parser.add_argument("--limit", type=int, default=0, help="最多检查多少条，0=不限")
        parser.add_argument("--apply", action="store_true", help="实际写库；默认仅审计")
        parser.add_argument(
            "--allow-medium", action="store_true",
            help="允许采用 medium 置信候选；默认仅 XML+DOI 的 high 候选自动矫正",
        )
        parser.add_argument("--csv", default="", help="将审计结果写入 CSV")
        parser.add_argument("--title", default="", help="单个 --doi 的人工确认标题")
        parser.add_argument("--source", default="manual", help="人工标题来源标记")

    def handle(self, *args, **opts):
        dois = [doi.strip().lower() for doi in opts["doi"] if doi.strip()]
        manual_title = clean_title(opts["title"])
        if manual_title and len(dois) != 1:
            raise CommandError("--title 必须与且只能与一个 --doi 一起使用")
        if manual_title and not is_usable_title(manual_title):
            raise CommandError("--title 不是可接受的标题")

        qs = Paper.objects.all().order_by("id")
        if dois:
            qs = qs.filter(doi__in=dois)
        else:
            qs = qs.annotate(title_len=Length("title")).filter(
                title_len__gte=max(1, opts["min_length"])
            )
        if opts["limit"]:
            qs = qs[:opts["limit"]]

        csv_file = None
        writer = None
        if opts["csv"]:
            csv_file = open(opts["csv"], "w", newline="", encoding="utf-8-sig")
            writer = csv.DictWriter(csv_file, fieldnames=(
                "doi", "openalex_id", "old_title", "reasons", "candidate_title",
                "candidate_source", "confidence", "evidence", "action",
            ))
            writer.writeheader()

        checked = suspect_count = corrected = candidates = 0
        try:
            for paper in qs.iterator(chunk_size=500):
                checked += 1
                assessment = assess_title(paper.title)
                if not assessment.suspect and not manual_title:
                    continue
                suspect_count += int(assessment.suspect)

                if manual_title:
                    candidate = FulltextTitleCandidate(
                        title=manual_title,
                        source=opts["source"][:30],
                        confidence="high",
                        evidence="manual_confirmed",
                    )
                else:
                    candidate = _fulltext_candidate(paper)
                if candidate:
                    candidates += 1

                allowed = candidate and (
                    candidate.confidence == "high"
                    or (opts["allow_medium"] and candidate.confidence == "medium")
                )
                should_correct = bool(
                    candidate and allowed
                    and (manual_title or is_better_title(paper.title, candidate.title))
                )
                action = "candidate"
                if not candidate:
                    action = "no_candidate"
                elif not allowed:
                    action = "needs_review"
                elif not should_correct:
                    action = "rejected"

                if opts["apply"]:
                    if should_correct:
                        if not paper.title_raw:
                            paper.title_raw = paper.title
                        paper.title = candidate.title
                        paper.title_source = candidate.source
                        paper.title_quality_status = Paper.TITLE_CORRECTED
                        paper.title_verified_at = timezone.now()
                        paper.save(update_fields=[
                            "title", "title_raw", "title_source", "title_quality_status",
                            "title_verified_at", "update_time",
                        ])
                        corrected += 1
                        action = "corrected"
                    elif assessment.suspect and paper.title_quality_status == Paper.TITLE_UNCHECKED:
                        paper.title_quality_status = Paper.TITLE_SUSPECT
                        paper.save(update_fields=["title_quality_status", "update_time"])
                        action = "marked_suspect"

                row = {
                    "doi": paper.doi,
                    "openalex_id": paper.openalex_id or "",
                    "old_title": paper.title_raw if action == "corrected" else paper.title,
                    "reasons": ",".join(assessment.reasons),
                    "candidate_title": candidate.title if candidate else "",
                    "candidate_source": candidate.source if candidate else "",
                    "confidence": candidate.confidence if candidate else "",
                    "evidence": candidate.evidence if candidate else "",
                    "action": action,
                }
                if writer:
                    writer.writerow(row)
                self.stdout.write(
                    f"[{action}] {paper.doi} len={assessment.plain_length} "
                    f"reasons={row['reasons']} candidate={row['candidate_source'] or '-'} "
                    f"confidence={row['confidence'] or '-'}"
                )
        finally:
            if csv_file:
                csv_file.close()

        mode = "apply" if opts["apply"] else "dry-run"
        self.stdout.write(self.style.SUCCESS(
            f"完成({mode}) checked={checked} suspect={suspect_count} "
            f"fulltext_candidates={candidates} corrected={corrected}"
        ))
