#!/usr/bin/env python3
"""Merge independent open-web term searches with procurement evidence."""

from __future__ import annotations

import json
from collections import Counter
from pathlib import Path


HERE = Path(__file__).resolve().parent
AUDIT = json.loads((HERE / "chain-audit-2026-08-21.json").read_text(encoding="utf-8"))
WEB = json.loads((HERE / "open-web-term-evidence-2026-08-21.json").read_text(encoding="utf-8"))
OUTPUT = HERE / "chain-term-audit-2026-08-21.json"


STATUS_BY_INDEX = {}
for label, indices in {
    "복수 업체 공용 사용 확인": [
        1, 2, 3, 6, 7, 9, 10, 14, 16, 17, 18, 20, 21, 22, 23, 24,
        29, 30, 33, 36, 37, 40, 41, 44, 45, 47, 48, 52, 53, 54, 55,
        56, 57, 58, 59, 61, 62, 63, 64, 65, 66, 67, 68, 71, 75, 76, 77,
    ],
    "제한적 업계 사용 확인": [12, 32, 34, 35],
    "특정 판매처식 세부명 가능성": [5, 8, 15, 25, 27, 28, 38, 39, 42, 50, 60, 69, 70],
    "사내 거래명만 확인": [11, 19, 31, 46, 51],
    "현재 사용 근거 불충분": [4, 13, 26, 43, 49, 72, 73, 74],
}.items():
    for index in indices:
        if index in STATUS_BY_INDEX:
            raise RuntimeError(f"duplicate terminology decision: {index}")
        STATUS_BY_INDEX[index] = label


SPECIAL_NOTES = {
    2: "신안제작소의 원재료와 세라실버의 완제품, TN의 까리노·혼체인 거래 기록에서 같은 별칭을 확인했다.",
    3: "엘렌쥬얼리와 비앤비골드 등 서로 다른 브랜드가 같은 이름을 사용한다. 다만 원재료 판매처는 아직 확인하지 못했다.",
    10: "신안제작소 외에 영툴의 은 미터 체인과 다린주얼리의 제작 문의에서 B형·비형고방 명칭을 확인했다.",
    12: "팝비즈의 반달고방 원재료와 TN의 까리노·혼체인 공급 기록이 함께 있어 단일 쇼핑몰 전용명으로 보기는 어렵다.",
    23: "H SILVER·클로첼 등 완제품 사용과 TN의 까리노 공급 기록이 있다. 이름은 유통되지만 구조 대응은 사진으로 다시 맞춰야 한다.",
    32: "정글비즈의 주얼리용 TR 체인과 TN의 혼체인 공급 기록만 유효하다. 산업용 TR 체인과 의류의 동명이의는 제외했다.",
    34: "대성재료상사 공개 원재료와 TN의 까리노·혼체인 공급 기록에서 확인했다. 공개 사용 범위는 아직 좁다.",
    35: "대성재료상사 공개 원재료와 TN의 혼체인 공급 기록에서 확인했다. 공개 사용 범위는 아직 좁다.",
    39: "국내에서는 Jewco 한 곳만 정확한 세부명을 사용했다. 해외 판매 결과는 국내 업계 사용 근거에서 제외했다.",
    47: "여러 업체가 같은 말을 쓰지만 십자 모티프와 볼체인의 조합을 뜻하는 상품도 섞인다. 구조 사진과 이름의 대응 확인이 필요하다.",
    60: "긴잠자리체인은 현재 KJF에서만 정확히 확인된다. 잠자리체인은 공용이지만 '긴' 세부명은 KJF식 명칭일 가능성이 있다.",
    63: "회사명 '한국아트체인' 결과는 제외했다. KJF의 원재료와 준주얼리의 체인 제품에서 주얼리 명칭 사용을 확인했다.",
    69: "가방·장식 롤의 동명이의는 제외했다. 주얼리 구조명으로 유효한 현재 공개 근거는 대성재료상사 한 곳이다.",
    72: "'스파커' 회사·게임·장비 결과는 모두 제외했다. 정확히 스파커체인이라는 현재 공개 사용은 찾지 못했다.",
    73: "정확한 이름과 별칭으로 검색했지만 현재 공개 사용과 POM 공급처 기록을 찾지 못했다.",
    74: "정확한 이름과 커팅·컷팅 별칭으로 검색했지만 현재 공개 사용과 POM 공급처 기록을 찾지 못했다.",
}


DEFAULT_NOTES = {
    "복수 업체 공용 사용 확인": "서로 다른 국내 판매업체·브랜드에서 같은 이름 또는 별칭을 확인했다. 특정 쇼핑몰 전용명으로 볼 근거가 없다.",
    "제한적 업계 사용 확인": "공개 판매 사용과 TN의 독립 공급처 기록이 함께 있다. 거래명으로 볼 수 있지만 공개 사례는 아직 적다.",
    "특정 판매처식 세부명 가능성": "정확한 세부명은 현재 한 공개 판매처에만 연결된다. 기본 계열명은 공용일 수 있으나 이 세부명은 정규화 검토가 필요하다.",
    "사내 거래명만 확인": "POM의 실제 공급처 기록은 있으나 국내 공개 웹에서 정확한 이름 사용을 찾지 못했다.",
    "현재 사용 근거 불충분": "체인북 또는 과거 후보 출처에는 있으나 현재 공개 웹과 POM 공급처에서 정확한 사용 근거를 찾지 못했다.",
}


EXCLUDED_DOMAINS = {
    "ko.aliexpress.com", "kr.dhgate.com", "ebay.com", "shopbop.com",
    "banjosbeadhouse.com.au", "continentalbeadsuppliers.com",
    "ball-chain.com", "ballandchainmiami.com", "ballchain.com",
    "kaggold.com",
}


EXCLUDED_BY_INDEX = {
    23: {"baltonj.com", "feelway.com"},
    32: {"lacentape.com", "tiramisustyle.com"},
    44: {"atelier-formare.com", "ballandchain.co.kr", "eqlstore.com"},
    63: {
        "catch.co.kr", "jobkorea.co.kr", "jobplanet.co.kr", "linkonbiz.com",
        "marketbz.com", "nicebizinfo.com", "saramin.co.kr", "thinkzon.com", "webify.kr",
    },
    69: {"danawa.com", "eplimfurniture.com"},
    72: {
        "114.ai.kr", "bridgevc.cafe24.com", "maple.inven.co.kr", "maplelandzzul.gg",
        "mapleplanet.gg", "marketbz.com", "planet-helper.com", "shop5.bridgevc.cafe24.com",
        "terms.naver.com", "thevc.kr", "thinkzon.com", "wadiz.kr", "wordrow.kr",
    },
}


AGGREGATORS = {
    "11st.co.kr", "a-bly.com", "coupang.com", "daangn.com", "enuri.com",
    "fleamarket.naver.com", "gsshop.com", "halfclub.com", "hmall.com", "hsmoa.com",
    "hsmoa.net", "lfmall.co.kr", "lotteimall.com", "lotteon.com", "musinsa.com",
    "prod.danawa.com", "shopping.naver.com", "shoppinghow.kakao.com", "ssg.com",
    "web.queenit.kr", "wconcept.co.kr", "zigzag.kr",
}


PREFERRED_BY_INDEX = {
    3: ["helenejw.com", "prod.danawa.com"],
    17: ["corsoaorder.townindex.kr", "eqlstore.com"],
    47: ["e-deasung.co.kr", "4xr.co.kr", "10x10.co.kr"],
    53: ["e-sang.co.kr", "gold-berry.com"],
    67: ["e-sang.co.kr", "kgolddia.com", "lovememonster.com"],
    68: ["e-deasung.co.kr", "hellomarket.com", "idus.com"],
    71: ["dodogold.com", "gembeads.co.kr", "sharmont.com"],
}


SUPPLEMENTARY_TERM_RESULTS = {
    63: [
        {
            "domain": "junjewelry.com",
            "title": "18K 아트 목걸이체인 4종 택1 — 총 4종 굵기의 아트체인",
            "url": "https://www.junjewelry.com/m/product_detail.html?brand_uid=1063240",
            "matched_term": "아트체인",
        }
    ]
}


NEW_RAW_DOMAINS = {
    6: {"beadsdome.com", "beadsgood.com", "beadsmaker.com"},
    10: {"youngtool.com"},
    12: {"popbeadsdome.com"},
    16: {"sdang24.com"},
    18: {"youngtool.com"},
    22: {"jewelryann.com"},
    29: {"beadsmaker.com", "deardeco.com", "jewelryann.com"},
    30: {"beadstamin.com", "youngtool.com"},
    32: {"junglebeads.co.kr"},
    33: {"youngtool.com"},
    37: {"beadsclub.co.kr", "beadssa.com", "danyirang.com", "youngtool.com"},
    41: {"youngtool.com"},
    44: {"beads-i.com", "wpack.kr"},
    45: {"gembeads.co.kr"},
    55: {"monogio.kr", "youngtool.com"},
    56: {"jewelryann.com"},
    57: {"jewelryann.com"},
    59: {"jewelryann.com"},
    77: {"beads-i.com"},
}


SELLER_LABELS = {
    "beadsdome.com": "비즈돔", "beadsgood.com": "비즈굿", "beadsmaker.com": "비즈메이커",
    "youngtool.com": "영툴", "popbeadsdome.com": "팝비즈도매", "sdang24.com": "스댕24",
    "jewelryann.com": "앤쥬얼리", "deardeco.com": "디어데코", "beadstamin.com": "비즈타민",
    "junglebeads.co.kr": "정글비즈", "beadsclub.co.kr": "비즈클럽", "beadssa.com": "비즈싸",
    "danyirang.com": "다니랑", "beads-i.com": "비즈아이", "wpack.kr": "더블유팩",
    "gembeads.co.kr": "젬비즈", "monogio.kr": "모노지오",
}


def term_evidence(index: int, open_row: dict, finished_listings: list[dict]) -> list[dict]:
    candidates = []
    for result in open_row["representative_results"] + SUPPLEMENTARY_TERM_RESULTS.get(index, []):
        if result["domain"] in EXCLUDED_DOMAINS or result["domain"] in EXCLUDED_BY_INDEX.get(index, set()):
            continue
        item = dict(result)
        item["evidence_kind"] = "국내 웹 사용"
        preferred = PREFERRED_BY_INDEX.get(index, [])
        if item["domain"] in preferred:
            item["priority"] = preferred.index(item["domain"])
        else:
            item["priority"] = 20 if item["domain"] in AGGREGATORS else 10
        candidates.append(item)
    for listing in finished_listings:
        candidates.append(
            {
                "domain": listing["seller"],
                "title": listing["title"],
                "url": listing["url"],
                "matched_term": "완제품 판매명",
                "evidence_kind": "국내 완제품 사용",
                "priority": 5,
            }
        )
    seen = set()
    selected = []
    for item in sorted(candidates, key=lambda value: (value["priority"], len(value["title"]))):
        group = item["domain"]
        if group in seen:
            continue
        seen.add(group)
        selected.append({key: value for key, value in item.items() if key != "priority"})
        if len(selected) == 3:
            break
    return selected


def extended_raw_listings(index: int, open_row: dict, existing: list[dict]) -> list[dict]:
    output = [dict(item) for item in existing if item["listing_type"] == "raw"]
    existing_domains = {item.get("seller") for item in output}
    for result in open_row["representative_results"]:
        if result["domain"] not in NEW_RAW_DOMAINS.get(index, set()):
            continue
        seller = SELLER_LABELS[result["domain"]]
        if seller in existing_domains:
            continue
        existing_domains.add(seller)
        output.append(
            {
                "seller": seller,
                "title": result["title"],
                "url": result["url"],
                "listing_type": "raw",
                "source": "independent open-web term search",
                "newly_found": True,
            }
        )
    return output


def main() -> None:
    if set(STATUS_BY_INDEX) != set(range(1, 78)):
        raise RuntimeError("terminology decisions must cover all 77 candidates exactly")
    web_by_name = {row["name"]: row for row in WEB["rows"]}
    rows = []
    for audit_row in AUDIT["rows"]:
        index = audit_row["index"]
        open_row = web_by_name[audit_row["name"]]
        status = STATUS_BY_INDEX[index]
        finished = [item for item in audit_row["representative_listings"] if item["listing_type"] == "finished"]
        raw = extended_raw_listings(index, open_row, audit_row["representative_listings"])
        rows.append(
            {
                **audit_row,
                "terminology_status": status,
                "terminology_reason": SPECIAL_NOTES.get(index, DEFAULT_NOTES[status]),
                "open_web_search_site_count": open_row["independent_domains"],
                "open_web_result_count": open_row["all_result_count"],
                "term_evidence": term_evidence(index, open_row, finished),
                "raw_procurement_listings": raw,
                "new_raw_supplier_count": sum(item.get("newly_found", False) for item in raw),
            }
        )
    status_counts = Counter(row["terminology_status"] for row in rows)
    result = {
        "title": "TN 체인 명칭 국내 업계 사용성 전수검색",
        "checked_on": "2026-08-21",
        "decision_question": "각 체인명이 특정 쇼핑몰의 상품명인가, 국내 주얼리 업계에서 독립적으로 사용되는 이름인가?",
        "method": {
            "independent_search": "77개 정식명과 모든 별칭을 각각 네이버 일반 웹에서 독립 검색하고, 0~1건·동명이의 항목은 별도 웹 검색으로 재확인",
            "term_rule": "서로 다른 국내 판매업체·브랜드의 정확한 명칭 사용을 공용 사용 근거로 채택; 동일 브랜드의 입점몰 복제와 동명이의는 제외",
            "procurement": "기존 5개 부자재몰 카테고리 대조 결과에 개별 검색으로 새로 찾은 국내 원재료 페이지를 병합",
            "limits": "공용 사용 확인은 이름이 여러 사업체에서 쓰인다는 뜻이며, 업체마다 구조·폭·컷팅이 완전히 같다는 표준 규격 보증은 아님",
        },
        "status_counts": dict(status_counts),
        "rows": rows,
    }
    OUTPUT.write_text(json.dumps(result, ensure_ascii=False, indent=2), encoding="utf-8")
    print(json.dumps(result["status_counts"], ensure_ascii=False, indent=2))
    print(f"wrote {OUTPUT}")


if __name__ == "__main__":
    main()
