#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""PChome 高業績商品主動反查 MOMO 候選，寫入外部報價層。"""

from __future__ import annotations

import os
from datetime import datetime, timezone
from typing import Any, Callable

from services.pchome_growth_same_item_reconciliation import (
    CONTRACT_VERSION,
    build_coverage_post_verifier,
    build_revenue_weighted_targets,
    ensure_evidence_receipt_table,
    new_reconciliation_identity,
    persist_reconciliation_receipts,
    readback_external_offer_candidates,
    verify_same_item_candidates,
)


def _int_env(name: str, default: int, *, minimum: int, maximum: int) -> int:
    try:
        value = int(os.getenv(name, str(default)))
    except (TypeError, ValueError):
        value = default
    return max(minimum, min(value, maximum))


def _float_env(name: str, default: float, *, minimum: float, maximum: float) -> float:
    try:
        value = float(os.getenv(name, str(default)))
    except (TypeError, ValueError):
        value = default
    return max(minimum, min(value, maximum))


def candidate_auto_compare_type(candidate: dict[str, Any]) -> str:
    auto_type = str(candidate.get("auto_compare_type") or "").strip()
    if auto_type in {"total_price", "unit_price"}:
        return auto_type
    if candidate.get("can_auto_compare") is True:
        return "total_price"
    return "manual_review"


def build_momo_backfill_targets(payload: dict[str, Any], limit: int) -> list[dict[str, Any]]:
    return build_revenue_weighted_targets(payload, limit)


def _default_build_payload(engine, limit: int) -> dict[str, Any]:
    from services.pchome_revenue_growth_service import build_pchome_growth_opportunities

    return build_pchome_growth_opportunities(engine, limit=limit)


def _default_search_candidates(targets: list[dict[str, Any]], limit: int):
    from services.momo_crawler import search_momo_products_for_pchome_products

    return search_momo_products_for_pchome_products(
        targets,
        max_products=limit,
        limit_per_product=_int_env(
            "PCHOME_GROWTH_MOMO_BACKFILL_LIMIT_PER_TERM",
            8,
            minimum=3,
            maximum=12,
        ),
        max_terms_per_product=_int_env(
            "PCHOME_GROWTH_MOMO_BACKFILL_MAX_TERMS",
            8,
            minimum=3,
            maximum=10,
        ),
        min_score=_float_env(
            "PCHOME_GROWTH_MOMO_BACKFILL_MIN_SCORE",
            0.45,
            minimum=0.35,
            maximum=0.8,
        ),
    )


def _default_sync_candidates(engine, candidates: list[dict[str, Any]]) -> dict[str, Any]:
    from services.external_market_offer_service import sync_targeted_momo_candidates_to_external_offers

    return sync_targeted_momo_candidates_to_external_offers(engine, candidates, dry_run=False)


def _default_sync_review_candidates(engine, candidates: list[dict[str, Any]]) -> dict[str, Any]:
    from services.external_market_offer_service import sync_targeted_momo_review_candidates_to_external_offers

    return sync_targeted_momo_review_candidates_to_external_offers(engine, candidates, dry_run=False)


def run_pchome_growth_momo_backfill(
    engine,
    *,
    limit: int = 12,
    build_payload_func: Callable[[Any, int], dict[str, Any]] | None = None,
    search_func: Callable[[list[dict[str, Any]], int], tuple[bool, str, list[dict[str, Any]]]] | None = None,
    sync_func: Callable[[Any, list[dict[str, Any]]], dict[str, Any]] | None = None,
    sync_review_func: Callable[[Any, list[dict[str, Any]]], dict[str, Any]] | None = None,
    readback_func: Callable[[Any, list[dict[str, Any]]], dict[str, Any]] | None = None,
) -> dict[str, Any]:
    """Run a bounded search, independent verifier, apply, and readback loop."""
    limit = max(1, min(int(limit or 12), 20))
    build_payload = build_payload_func or _default_build_payload
    search_candidates = search_func or _default_search_candidates
    sync_candidates = sync_func or _default_sync_candidates
    sync_review_candidates = sync_review_func or _default_sync_review_candidates
    readback_candidates = readback_func or readback_external_offer_candidates
    identity = new_reconciliation_identity()
    generated_at = datetime.now(timezone.utc).isoformat()

    # The command center contract is top-50. Ranking a smaller pre-sorted slice
    # can hide the highest-revenue unresolved products.
    before_payload = build_payload(engine, 50)
    targets = build_momo_backfill_targets(before_payload, limit)
    schema_receipt = ensure_evidence_receipt_table(engine)
    if not schema_receipt.get("success"):
        return {
            "success": False,
            "message": "同商品收據資料層尚未就緒，本輪已安全停止且沒有寫入價格。",
            "data": {
                "contract_version": CONTRACT_VERSION,
                "identity": identity,
                "terminal_status": "blocked_receipt_schema_unavailable",
                "target_count": len(targets),
                "scanned_products": 0,
                "candidate_count": 0,
                "auto_compare_count": 0,
                "review_count": 0,
                "external_offer_sync": {"status": "not_executed", "written_count": 0},
                "review_candidate_sync": {"status": "not_executed", "written_count": 0},
                "receipt_schema": schema_receipt,
                "before_stats": before_payload.get("stats") or {},
                "after_stats": before_payload.get("stats") or {},
                "targets": targets,
                "review_candidates": [],
            },
        }

    if targets:
        search_success, search_message, candidates = search_candidates(targets, limit)
    else:
        search_success, search_message, candidates = (
            True,
            "目前 TOP 50 沒有未解決的同商品對應。",
            [],
        )
    candidates = list(candidates or [])
    candidate_verification = verify_same_item_candidates(
        targets,
        candidates,
        identity=identity,
    )
    auto_candidates = list(candidate_verification.get("verified_candidates") or [])
    exact_candidates = [item for item in auto_candidates if candidate_auto_compare_type(item) == "total_price"]
    unit_candidates = [item for item in auto_candidates if candidate_auto_compare_type(item) == "unit_price"]
    review_candidates = list(candidate_verification.get("review_candidates") or [])
    auto_candidates = [*exact_candidates, *unit_candidates]
    external_offer_sync = {
        "success": True,
        "status": "not_found",
        "written_count": 0,
        "message": "已搜尋 MOMO，但尚未找到可自動寫入的同款或單位價候選。",
    }
    if auto_candidates:
        external_offer_sync = sync_candidates(engine, auto_candidates)
        external_offer_sync["independent_readback_required"] = True
        external_offer_sync["independent_readback"] = readback_candidates(
            engine,
            auto_candidates,
        )
    review_candidate_sync = {
        "success": True,
        "status": "not_found",
        "written_count": 0,
        "message": "沒有需要保存的待確認候選。",
    }
    if review_candidates:
        review_candidate_sync = sync_review_candidates(engine, review_candidates)

    after_payload = build_payload(engine, 50)
    post_verifier = build_coverage_post_verifier(
        before_payload=before_payload,
        after_payload=after_payload,
        verified_candidates=auto_candidates,
        sync_result=external_offer_sync,
    )
    written_count = int(external_offer_sync.get("written_count") or 0)
    after_snapshot = post_verifier.get("after") or {}
    unresolved_after = int(after_snapshot.get("candidate_validation_count") or 0) + int(
        after_snapshot.get("unmatched_count") or 0
    )
    if not post_verifier.get("all_checks_passed"):
        terminal_status = "partial_post_verifier_failed"
        next_machine_action = "retry_bounded_batch_after_drift_diagnosis"
    elif written_count > 0:
        terminal_status = (
            "verified_with_coverage_gain"
            if max(
                float(post_verifier.get("count_coverage_delta") or 0),
                float(post_verifier.get("revenue_coverage_delta") or 0),
            ) > 0
            else "verified_write_readback"
        )
        next_machine_action = (
            "continue_next_revenue_weighted_batch"
            if unresolved_after
            else "maintain_same_item_coverage"
        )
    elif unresolved_after:
        terminal_status = "degraded_no_safe_candidate"
        next_machine_action = "retry_unresolved_batch_with_fresh_source_evidence"
    else:
        terminal_status = "verified_no_work_remaining"
        next_machine_action = "maintain_same_item_coverage"

    run_receipt = {
        "contract_version": CONTRACT_VERSION,
        "generated_at": generated_at,
        "identity": identity,
        "source_receipt": {
            "source": "momo_targeted_search",
            "search_success": bool(search_success),
            "search_message": search_message,
            "target_count": len(targets),
            "candidate_count": len(candidates),
        },
        "normalization": {
            "target_count": len(targets),
            "candidate_count": len(candidates),
            "candidate_fingerprint_count": len({
                str(item.get("same_item_candidate_fingerprint") or "")
                for item in auto_candidates
                if item.get("same_item_candidate_fingerprint")
            }),
        },
        "candidate_verification": candidate_verification,
        "coverage_diff": {
            "comparison_ready_delta": post_verifier.get("comparison_ready_delta"),
            "count_coverage_delta": post_verifier.get("count_coverage_delta"),
            "revenue_coverage_delta": post_verifier.get("revenue_coverage_delta"),
        },
        "risk_policy": {
            "risk": "medium",
            "bounded_target_limit": limit,
            "formal_price_write_requires_independent_verifier": True,
            "pixelrag_direct_price_write_allowed": False,
        },
        "check_mode": {
            "receipt_schema_ready": bool(schema_receipt.get("success")),
            "verified_candidate_count": len(auto_candidates),
            "blocked_candidate_count": int(candidate_verification.get("blocked_candidate_count") or 0),
        },
        "execution": {
            "idempotent_bounded_execution": True,
            "written_count": written_count,
            "review_candidate_written_count": int(review_candidate_sync.get("written_count") or 0),
            "sync_status": external_offer_sync.get("status"),
        },
        "post_verifier": post_verifier,
        "terminal": {
            "status": terminal_status,
            "next_machine_action": next_machine_action,
            "unresolved_after": unresolved_after,
        },
    }
    receipt_persistence = persist_reconciliation_receipts(
        engine,
        targets=targets,
        run_receipt=run_receipt,
    )
    if not receipt_persistence.get("success"):
        terminal_status = "partial_receipt_persistence_failed"
        next_machine_action = "retry_receipt_persistence_before_next_apply"
        run_receipt["terminal"] = {
            "status": terminal_status,
            "next_machine_action": next_machine_action,
            "unresolved_after": unresolved_after,
        }

    success = bool(
        post_verifier.get("all_checks_passed")
        and receipt_persistence.get("success")
    )
    message = (
        f"已依業績權重掃描 {len(targets)} 個商品，找到 {len(candidates)} 筆候選，"
        f"獨立驗證 {len(auto_candidates)} 筆，正式寫入並回讀 {post_verifier.get('readback_pass_count', 0)} 筆。"
    )
    if not search_success and not candidates:
        message = search_message or "已完成 bounded 搜尋，但本輪沒有安全候選。"

    return {
        "success": success,
        "message": message,
        "data": {
            "contract_version": CONTRACT_VERSION,
            "identity": identity,
            "terminal_status": terminal_status,
            "next_machine_action": next_machine_action,
            "search_success": bool(search_success),
            "search_message": search_message,
            "scanned_products": len(targets),
            "target_count": len(targets),
            "candidate_count": len(candidates),
            "exact_compare_count": len(exact_candidates),
            "unit_compare_count": len(unit_candidates),
            "auto_compare_count": len(auto_candidates),
            "review_count": len(review_candidates),
            "external_offer_sync": external_offer_sync,
            "review_candidate_sync": review_candidate_sync,
            "before_stats": before_payload.get("stats") or {},
            "after_stats": after_payload.get("stats") or {},
            "post_verifier": post_verifier,
            "receipt_schema": schema_receipt,
            "receipt_persistence": receipt_persistence,
            "reconciliation_receipt": run_receipt,
            "targets": targets,
            "review_candidates": review_candidates[:8],
        },
    }
