"""
LinkedIn Morocco dealer discovery.

Preview-first workflow:
- Connect to an already-open AdsPower browser by profile ID.
- Search LinkedIn company results with Morocco-wide dealer/importer keywords.
- Score and de-duplicate candidates before opening company pages.
- Reject OEM local brand-country pages such as BYD Maroc or BMW Maroc.
- Optionally deep-scrape company About pages and write records to the LinkedIn sheet.
"""
import argparse
import json
import random
import re
import sys
import time
from datetime import datetime, timezone
from pathlib import Path

import sys
sys.path.append(str(Path(__file__).resolve().parents[1]))
from common.artifact_manager import resolve_artifact_path, create_backup_once
from typing import Any, Dict, List, Optional, Set, Tuple
from urllib.parse import quote_plus, urlparse

if hasattr(sys.stdout, "reconfigure"):
    sys.stdout.reconfigure(encoding="utf-8")
if hasattr(sys.stderr, "reconfigure"):
    sys.stderr.reconfigure(encoding="utf-8")

import pandas as pd
import requests
from playwright.sync_api import Page, sync_playwright

try:
    from ..common import append_records, resolve_workbook_path
    from . import discovery_common as dc
except ImportError:
    sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
    from common import append_records, resolve_workbook_path
    from scraper import discovery_common as dc

DEFAULT_EXCEL = ""
DEFAULT_SHEET = "LinkedIn"
DEFAULT_CITY_SCOPE = "摩洛哥全国"

DEFAULT_KEYWORDS = [
    "showroom auto Maroc",
    "concessionnaire automobile Maroc",
    "concessionnaire multimarque Maroc",
    "distributeur automobile Maroc",
    "importateur automobile Maroc",
    "groupe automobile Maroc",
    "voiture occasion Maroc",
    "concessionnaire utilitaire Maroc",
    "La Continentale Auto Maroc",
    "Prince Auto Maroc",
    "Kifal Auto Maroc",
    "Bamotors Maroc",
    "SMEIA Maroc",
    "Sopriam Maroc",
    "Auto Hall Maroc",
    "Auto Nejma Maroc",
    "Centrale Automobile Chérifienne Maroc",
]

MOROCCO_TERMS = {
    "maroc", "morocco", "casablanca", "rabat", "marrakech", "tanger",
    "fes", "fès", "agadir", "meknes", "oujda", "kenitra", "tetouan",
    "nador", "safi", "settat", "dar bouazza", "el jadida",
}

CHINA_BRAND_TERMS = {
    "baic", "byd", "changan", "chery", "dfsk", "dongfeng", "foton", "forland",
    "gac", "geely", "great wall", "haval", "jac", "jetour", "maxus", "mg",
    "omoda", "saic", "sitrak", "sinotruk", "shacman", "faw", "yutong",
    "king long", "golden dragon", "higer", "wuling",
}

NON_CHINA_BRAND_TERMS = {
    "audi", "bmw", "chevrolet", "citroen", "citroën", "dacia", "daf", "fiat",
    "ford", "hino", "honda", "hyundai", "isuzu", "iveco", "jeep", "kia", "man",
    "mazda", "mercedes", "mercedes-benz", "mitsubishi", "nissan", "opel", "peugeot",
    "renault", "scania", "seat", "skoda", "suzuki", "toyota", "volkswagen", "volvo",
}

OEM_BRANCH_BRANDS = CHINA_BRAND_TERMS | NON_CHINA_BRAND_TERMS

INDEPENDENT_CHANNEL_CLUES = {
    "auto hall", "smaa", "smeia", "auto nejma", "la continentale", "prince auto",
    "kifal", "autochek", "bugshan", "bamotors", "sopriam", "cac",
    "centrale automobile", "univers motors", "m-automotiv", "m automotiv",
}

HIGH_INTENT_TERMS = {
    "concessionnaire", "dealer", "distributeur", "distribution", "importateur",
    "importation", "showroom", "groupe", "group", "automobile", "auto", "retail",
    "vente", "voiture", "occasion", "reprise", "multimarque", "service après-vente",
    "après-vente", "pieces", "pièces", "fleet", "flotte", "utilitaire", "camion",
}

LOW_VALUE_TERMS = {
    "aerospace", "aéronautique", "assurance", "insurance", "software", "marketing",
    "emailing", "real estate", "immobilier", "building materials", "construction materials",
    "diagnostic", "spare parts only", "pièces uniquement",
}

CITY_PATTERNS = [
    "Casablanca", "Rabat", "Marrakech", "Tanger", "Fes", "Fès", "Agadir", "Meknes",
    "Oujda", "Kenitra", "Tetouan", "Tétouan", "Nador", "Safi", "Settat",
    "Dar Bouazza", "El Jadida",
]


def compact_text(value: str) -> str:
    return re.sub(r"[^a-z0-9]+", "", value.casefold())


def normalize_linkedin_url(url: str) -> str:
    return dc.normalize_linkedin_url(url)


def load_existing_links(excel_path: str, sheet_name: str) -> Set[str]:
    path = Path(excel_path)
    if not path.exists():
        return set()
    try:
        df = pd.read_excel(path, sheet_name=sheet_name)
    except Exception:
        return set()
    link_col = "linkin链接" if "linkin链接" in df.columns else "主页/链接"
    if link_col not in df.columns:
        return set()
    return {
        normalized
        for normalized in (normalize_linkedin_url(str(value).strip()) for value in df[link_col].tolist())
        if normalized
    }


def load_blocklist_json(blocklist_path: Optional[str]) -> Set[str]:
    return dc.load_blocklist_json(blocklist_path, normalizer=dc.normalize_linkedin_url)


def matched_terms(text: str, terms: Set[str]) -> List[str]:
    text_lower = text.casefold()
    return sorted(term for term in terms if term.casefold() in text_lower)


def looks_like_oem_local_branch(name: str, url: str, text: str = "") -> Tuple[bool, str]:
    return dc.looks_like_oem_local_branch(name, url, text)


def score_candidate(candidate: Dict[str, Any], existing_links: Set[str], existing_names: Optional[Set[str]] = None) -> Dict[str, Any]:
    name = candidate.get("name", "")
    href = candidate.get("href", "")
    text = candidate.get("text", "")
    queries = candidate.get("source_queries", [])
    entity_text = " ".join([name, href, text]).casefold()
    query_text = " ".join(queries).casefold()
    combined = " ".join([entity_text, query_text]).casefold()

    score = 0
    reasons: List[str] = []
    risks: List[str] = []
    recommended_action = "preview_only"

    normalized = normalize_linkedin_url(href)
    existing_names = existing_names or set()
    name_key = dc.normalized_company_key(name)
    if normalized in existing_links:
        return {
            **candidate,
            "score": -10,
            "score_reasons": ["Already exists in workbook"],
            "risk_flags": ["duplicate_existing_link"],
            "recommended_action": "skip_existing",
        }
    if name_key and name_key in existing_names:
        return {
            **candidate,
            "score": -9,
            "score_reasons": ["Company name already exists in workbook"],
            "risk_flags": ["duplicate_existing_name"],
            "recommended_action": "skip_existing",
        }

    is_oem, brand = looks_like_oem_local_branch(name, href, text)
    brand_terms = matched_terms(entity_text, OEM_BRANCH_BRANDS)
    if is_oem:
        score -= 8
        risks.append(f"疑似品牌当地分公司/官方页，排除: {brand}")
        recommended_action = "skip_brand_branch"
    ownership_needs_review = (not is_oem) and brand_terms and not any(clue in entity_text for clue in INDEPENDENT_CHANNEL_CLUES)

    morocco_terms = matched_terms(entity_text, MOROCCO_TERMS)
    if morocco_terms:
        score += 2
        reasons.append("Morocco signal: " + ", ".join(morocco_terms[:4]))

    intent_terms = matched_terms(entity_text, HIGH_INTENT_TERMS)
    if intent_terms:
        score += min(5, len(intent_terms))
        reasons.append("dealer/import/channel signal: " + ", ".join(intent_terms[:6]))

    china_terms = matched_terms(entity_text, CHINA_BRAND_TERMS)
    if china_terms and not is_oem:
        score += 2
        reasons.append("China-brand context inside dealer channel: " + ", ".join(china_terms[:4]))

    canonical_name = dc.canonical_dealer_name(name, text)
    if canonical_name and canonical_name != name:
        candidate = {**candidate, "canonical_name": canonical_name}
        reasons.append(f"mapped to independent dealer group: {canonical_name}")

    if any(clue in entity_text for clue in INDEPENDENT_CHANNEL_CLUES):
        score += 4
        reasons.append("known independent Moroccan auto channel")

    low_terms = matched_terms(entity_text, LOW_VALUE_TERMS)
    if low_terms:
        score -= min(4, len(low_terms) * 2)
        risks.append("possible low-value/non-dealer result: " + ", ".join(low_terms[:4]))

    channel_terms = matched_terms(entity_text, {"concessionnaire", "dealer", "showroom", "new vehicle", "new cars", "vehicules neufs", "v?hicules neufs", "vente", "importateur", "distributeur", "distribution", "groupe", "group", "multimarque", "multi-brand"})
    new_vehicle_needs_review = not channel_terms

    if ownership_needs_review and dc.should_request_manual_review(score, recommended_action, reasons):
        dc.add_manual_review_flag(risks, "ownership")
    if new_vehicle_needs_review and dc.should_request_manual_review(score, recommended_action, reasons):
        dc.add_manual_review_flag(risks, "new_vehicle")

    if not reasons:
        reasons.append("weak LinkedIn search signal; low priority unless deeper evidence confirms channel value")

    if recommended_action == "preview_only" and score >= 5:
        recommended_action = "deep_scrape"

    return {
        **candidate,
        "score": score,
        "score_reasons": reasons,
        "risk_flags": risks,
        "recommended_action": recommended_action,
    }


def get_active_ws_endpoint(base_url: str, profile_id: str) -> str:
    return dc.get_active_ws_endpoint(base_url, profile_id)


def dismiss_linkedin_dialogs(page: Page) -> None:
    labels = [
        "继续前往公司主页",
        "Continue to company page",
        "Continuer vers la page de l’entreprise",
        "关闭",
        "Close",
    ]
    for label in labels:
        try:
            page.get_by_text(label, exact=False).first.click(timeout=1500)
            time.sleep(0.5)
        except Exception:
            pass


def collect_company_results(page: Page, query: str, max_results: int) -> List[Dict[str, Any]]:
    url = f"https://www.linkedin.com/search/results/companies/?keywords={quote_plus(query)}&origin=GLOBAL_SEARCH_HEADER"
    print(f"搜索 LinkedIn: {query}", flush=True)
    page.goto(url, wait_until="domcontentloaded", timeout=60000)
    time.sleep(random.uniform(3, 5))
    dismiss_linkedin_dialogs(page)

    for _ in range(2):
        page.mouse.wheel(0, 900)
        time.sleep(random.uniform(1, 1.8))

    raw = page.evaluate(
        """
        () => {
          const anchors = Array.from(document.querySelectorAll('a[href*="/company/"]'));
          return anchors.map((a) => {
            let node = a.closest('li') || a.closest('.reusable-search__result-container') || a.parentElement;
            let text = '';
            let cur = node;
            for (let i = 0; i < 7 && cur; i++) {
              const value = (cur.innerText || '').trim();
              if (value.length > text.length) text = value;
              if (value.length > 80) break;
              cur = cur.parentElement;
            }
            return { href: a.href || '', anchorText: (a.innerText || '').trim(), text };
          });
        }
        """
    )

    results: List[Dict[str, Any]] = []
    seen: Set[str] = set()
    for item in raw:
        href = normalize_linkedin_url(item.get("href", ""))
        if not href or href in seen:
            continue
        text = re.sub(r"\n{2,}", "\n", item.get("text", "")).strip()
        lines = [line.strip() for line in text.splitlines() if line.strip()]
        anchor = re.sub(r"\s+", " ", item.get("anchorText", "")).strip()
        if lines:
            name = lines[0]
        elif anchor and len(anchor) <= 80:
            name = anchor
        else:
            name = anchor.split("  ")[0].strip()
        name = re.sub(r"\s+\d+(?:\.\d+)?\s*万?\s*位关注者.*$", "", name).strip()
        if not name or name.lower() in {"linkedin", "home", "search"}:
            continue
        seen.add(href)
        results.append({
            "name": name,
            "href": href,
            "text": text[:1000],
            "source_queries": [query],
        })
        if len(results) >= max_results:
            break
    print(f"  收集到 {len(results)} 个公司候选", flush=True)
    return results


def merge_candidates(items: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
    by_href: Dict[str, Dict[str, Any]] = {}
    for item in items:
        href = item["href"]
        if href not in by_href:
            by_href[href] = item
            continue
        existing = by_href[href]
        existing["source_queries"] = sorted(set(existing.get("source_queries", []) + item.get("source_queries", [])))
        snippets = existing.setdefault("snippets", [existing.get("text", "")])
        if item.get("text") and item["text"] not in snippets:
            snippets.append(item["text"])
    return list(by_href.values())


def extract_lines(text: str) -> List[str]:
    return [line.strip() for line in text.splitlines() if line.strip()]


def value_after_label(lines: List[str], label: str) -> str:
    for idx, line in enumerate(lines):
        if line.strip() == label and idx + 1 < len(lines):
            return lines[idx + 1].strip()
    return ""


def extract_urls(text: str) -> List[str]:
    urls = re.findall(r"https?://[^\s<>()]+", text)
    cleaned: List[str] = []
    for url in urls:
        url = url.rstrip(".,，。;；")
        if "linkedin.com" not in url and url not in cleaned:
            cleaned.append(url)
    return cleaned


def extract_email(text: str) -> str:
    match = re.search(r"[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}", text)
    return match.group(0) if match else ""


def extract_phone(text: str) -> str:
    explicit = re.search(r"(?:Téléphone|电话|Tél|Tel|WhatsApp|电话号码)[:：]?\s*([+()\d][+()\d\s.-]{6,}\d)", text, re.I)
    if explicit:
        return re.sub(r"\s+", " ", explicit.group(1)).strip()
    match = re.search(r"(?:\+212|0)\s?\d[\d\s.-]{6,}\d", text)
    return re.sub(r"\s+", " ", match.group(0)).strip() if match else ""


def extract_city(text: str, fallback: str = DEFAULT_CITY_SCOPE) -> str:
    hits = []
    for city in CITY_PATTERNS:
        if re.search(rf"\b{re.escape(city)}\b", text, re.I) and city not in hits:
            hits.append(city)
    return " / ".join(hits[:4]) if hits else fallback


def extract_followers(text: str) -> str:
    match = re.search(r"([\d,.]+\s*万?|[\d,.]+)\s*位关注者", text)
    return match.group(1).strip() + "位关注者" if match else ""


def classify_customer_type(text: str) -> str:
    return dc.classify_customer_type(str(text or ""))


def summarize_business(text: str) -> str:
    lower = text.casefold()
    parts: List[str] = []
    if any(term in lower for term in ["occasion", "reprise", "voiture d'occasion"]):
        parts.append("二手车买卖/置换")
    if any(term in lower for term in ["concessionnaire", "showroom", "vente automobile", "汽车零售"]):
        parts.append("汽车销售/showroom")
    if any(term in lower for term in ["importateur", "distribution", "distributeur"]):
        parts.append("进口/分销")
    if any(term in lower for term in ["utilitaire", "camion", "truck", "fleet", "flotte"]):
        parts.append("商用车/车队")
    china = matched_terms(text, CHINA_BRAND_TERMS)
    non_china = matched_terms(text, NON_CHINA_BRAND_TERMS)
    if china:
        parts.append("涉及中国品牌: " + ", ".join(china[:4]))
    elif non_china:
        parts.append("主要品牌信号: " + ", ".join(non_china[:5]))
    return "；".join(parts) if parts else "LinkedIn 汽车渠道线索，主营业务证据不足，需深搜确认"


def deep_scrape_about(page: Page, candidate: Dict[str, Any], country: str) -> Dict[str, Any]:
    about_url = candidate["href"].rstrip("/") + "/about/"
    print(f"深采 LinkedIn About: {candidate['name']}", flush=True)
    page.goto(about_url, wait_until="domcontentloaded", timeout=60000)
    time.sleep(random.uniform(3, 5))
    dismiss_linkedin_dialogs(page)
    time.sleep(random.uniform(1, 2))

    title = page.title()
    text = page.locator("body").inner_text(timeout=10000)
    if not text.strip():
        candidate.setdefault("risk_flags", [])
        dc.add_manual_review_flag(candidate["risk_flags"], "detail")
    lines = extract_lines(text)
    urls = extract_urls(text)
    website = value_after_label(lines, "网站") or (urls[0] if urls else "")
    phone = value_after_label(lines, "电话") or extract_phone(text)
    email = extract_email(text)
    industry = value_after_label(lines, "行业")
    city = extract_city(text)
    followers = extract_followers(text)
    company_type = classify_customer_type(text + "\n" + candidate.get("text", ""))
    business = summarize_business(text + "\n" + candidate.get("text", ""))

    is_oem, brand = looks_like_oem_local_branch(candidate.get("name", ""), candidate.get("href", ""), text)
    if is_oem:
        candidate["recommended_action"] = "skip_brand_branch"
        candidate.setdefault("risk_flags", []).append(f"深采确认疑似品牌官方页: {brand}")
        dc.add_manual_review_flag(candidate["risk_flags"], "ownership")

    note_parts = [
        "LinkedIn深采",
        f"行业：{industry}" if industry else "",
        f"关注者：{followers}" if followers else "",
        f"网站：{website}" if website else "",
        f"来源搜索词：{', '.join(candidate.get('source_queries', []))}",
        f"评分：{candidate.get('score')}；原因：{'; '.join(candidate.get('score_reasons', []))}",
    ]
    if candidate.get("risk_flags"):
        note_parts.append("风险：" + "; ".join(candidate["risk_flags"]))
    note = " | ".join(part for part in note_parts if part)

    return {
        "公司名称": candidate.get("canonical_name") or candidate.get("name", ""),
        "国家": country,
        "城市": city,
        "客户类型": company_type,
        "linkin链接": candidate.get("href", ""),
        "联系人": "",
        "职位": "",
        "公司公共电话": phone,
        "公司公共邮箱（任一有效即可）": email,
        "个人邮箱": "",
        "公司主营业务": business,
        "建联状态": "未联系",
        "备注": note[:1200],
        "_debug_title": title,
        "_about_head": lines[:60],
    }


def search_linkedin_dealers(
    profile_id: str,
    keywords: Optional[List[str]],
    ads_power_url: str,
    excel_path: str,
    sheet_name: str,
    max_results: int,
    max_results_per_query: int,
    min_score: int,
    deep_scrape: bool,
    country: str,
    blocklist_json: Optional[str] = None,
) -> Dict[str, Any]:
    existing_identity = dc.load_existing_identity(
        excel_path=excel_path,
        sheet_name=sheet_name,
        link_columns=["linkin链接", "主页/链接"],
        name_columns=["公司名称", "客户姓名/公司"],
    )
    existing_links = existing_identity["links"]
    existing_names = existing_identity["names"]
    blocklist_links = load_blocklist_json(blocklist_json)
    existing_links = existing_links | blocklist_links
    print(f"已从 {sheet_name} Sheet 加载 {len(existing_links)} 个 LinkedIn 现有链接、{len(existing_names)} 个公司名用于去重", flush=True)
    if blocklist_json:
        print(f"  其中来自黑名单 JSON: {len(blocklist_links)} 个", flush=True)
    ws_endpoint = get_active_ws_endpoint(ads_power_url, profile_id)

    playwright = sync_playwright().start()
    browser = playwright.chromium.connect_over_cdp(ws_endpoint)
    context = browser.contexts[0] if browser.contexts else browser.new_context()
    page = context.new_page()
    page.set_viewport_size({"width": 1366, "height": 850})

    queries = keywords or DEFAULT_KEYWORDS
    raw_candidates: List[Dict[str, Any]] = []
    search_log: List[Dict[str, Any]] = []
    records: List[Dict[str, Any]] = []

    try:
        for query in queries:
            items = collect_company_results(page, query, max_results_per_query)
            raw_candidates.extend(items)
            search_log.append({"query": query, "found": len(items), "url": page.url, "title": page.title()})
            time.sleep(random.uniform(2, 4))

        merged = merge_candidates(raw_candidates)
        scored_all = [score_candidate(item, existing_links, existing_names) for item in merged]
        skipped_existing = [c for c in scored_all if c.get("recommended_action") == "skip_existing"]
        active_scored = [c for c in scored_all if c.get("recommended_action") != "skip_existing"]
        scored = dc.dedupe_dealer_groups(active_scored)
        scored.sort(key=lambda c: (-int(c.get("score", 0)), c.get("name", "")))

        selected = [
            c for c in scored
            if c.get("recommended_action") == "deep_scrape" and int(c.get("score", 0)) >= min_score
        ][:max_results]

        if deep_scrape:
            for candidate in selected:
                record = deep_scrape_about(page, candidate, country=country)
                if candidate.get("recommended_action") == "skip_brand_branch":
                    continue
                records.append(record)
                time.sleep(random.uniform(2, 4))
    finally:
        try:
            page.close()
        except Exception:
            pass
        playwright.stop()

    return {
        "generated_at": datetime.now(timezone.utc).isoformat(),
        "source": "LinkedIn company search via AdsPower active profile",
        "summary": {
            "profile_id": profile_id,
            "queries": queries,
            "raw_candidates": len(raw_candidates),
            "candidate_count": len(scored),
            "selected_for_deep_scrape": len(selected),
            "deep_scrape": deep_scrape,
            "record_count": len(records),
            "min_score": min_score,
            "max_results": max_results,
            "existing_links": len(existing_links),
            "existing_names": len(existing_names),
            "skipped_existing": len(skipped_existing),
        },
        "search_log": search_log,
        "candidates": scored,
        "skipped_existing": skipped_existing,
        "selected_candidates": selected,
        "records": records,
    }


def parse_keywords(value: str) -> Optional[List[str]]:
    if not value.strip():
        return None
    return [item.strip() for item in value.split(",") if item.strip()]


def main() -> None:
    parser = argparse.ArgumentParser(description="LinkedIn Morocco dealer discovery, preview-first")
    parser.add_argument("--profile-id", required=True, help="AdsPower profile ID; must already be open and logged in")
    parser.add_argument("--ads-power-url", default="http://127.0.0.1:50325", help="AdsPower local API URL")
    parser.add_argument("--keywords", default="", help="Comma-separated LinkedIn company search keywords")
    parser.add_argument("--excel", default=DEFAULT_EXCEL, help="Workbook for duplicate checking and optional write-back")
    parser.add_argument("--sheet", default=DEFAULT_SHEET, help="Target sheet, normally LinkedIn")
    parser.add_argument("--country", default="摩洛哥", help="Country value for records")
    parser.add_argument("--max-results", type=int, default=10, help="Maximum deep-scraped records")
    parser.add_argument("--max-results-per-query", type=int, default=5, help="Company links collected per query")
    parser.add_argument("--min-score", type=int, default=5, help="Minimum score for deep-scrape selection")
    parser.add_argument("--deep-scrape", action="store_true", help="Open selected company About pages and build records")
    parser.add_argument("--write-excel", action="store_true", help="Write deep-scraped records to the workbook")
    parser.add_argument("--blocklist-json", default="", help="Optional JSON file with additional LinkedIn URLs to skip")
    parser.add_argument("--run-id", default="", help="Run ID used for runs/YYYYMMDD/<run_id>/ artifacts.")
    parser.add_argument("--output", default="linkedin_candidate_preview.json", help="Output JSON path")

    args = parser.parse_args()

    read_workbook_info = resolve_workbook_path(args.excel, create_from_template=False)
    if read_workbook_info.get("path"):
        args.excel = str(read_workbook_info["path"])
        print(f"Workbook for duplicate checking: {args.excel} ({read_workbook_info['source']})", flush=True)

    result = search_linkedin_dealers(
        profile_id=args.profile_id,
        keywords=parse_keywords(args.keywords),
        ads_power_url=args.ads_power_url,
        excel_path=args.excel,
        sheet_name=args.sheet,
        max_results=args.max_results,
        max_results_per_query=args.max_results_per_query,
        min_score=args.min_score,
        deep_scrape=args.deep_scrape,
        country=args.country,
        blocklist_json=args.blocklist_json or None,
    )

    output_path = resolve_artifact_path(args.output, kind="scraper_preview", default_name=Path(args.output).name, run_id=args.run_id or None)
    output_path.parent.mkdir(parents=True, exist_ok=True)
    output_path.write_text(json.dumps(result, ensure_ascii=False, indent=2), encoding="utf-8")
    print(f"已保存 LinkedIn 候选结果: {output_path}", flush=True)

    if args.write_excel:
        write_workbook_info = resolve_workbook_path(args.excel, create_from_template=True)
        if write_workbook_info.get("path"):
            args.excel = str(write_workbook_info["path"])
            print(f"Workbook for writing: {args.excel} ({write_workbook_info['source']})", flush=True)
        if not args.deep_scrape:
            print("未写入 Excel：需要先使用 --deep-scrape 生成 records。", flush=True)
        elif not result.get("records"):
            print("未写入 Excel：没有可写入记录。", flush=True)
        else:
            write_result = append_records(
                excel_path=args.excel,
                sheet_name=args.sheet,
                records=result["records"],
                dedup_keys=["公司名称", "linkin链接"],
            )
            print(f"Excel 回写结果: {write_result}", flush=True)
    else:
        print("默认预览模式：未写入 Excel。确认要入表时再使用 --deep-scrape --write-excel。", flush=True)


if __name__ == "__main__":
    main()





