"""Orchestrate one idempotent daily ROI metric and approval batch."""

from __future__ import annotations

import hashlib
import logging
import os
import re
from datetime import datetime, timedelta
from pathlib import Path
from typing import Any
from zoneinfo import ZoneInfo

from storage import initialize_schema, load_managed_accounts
from tencent_client import ACTIVE_STATUS, TencentClient

from .agency_delivery import agency_route_key, publish_agency_reports
from .config import AgencyWebhookConfig, RoiConfig
from .data_source import (
    ODPSClient,
    date_window,
    fetch_ad_age,
    fetch_daily_data,
    resolve_end_date,
)
from .feishu import RoiFeishuPublisher
from .metrics import ENTITY_SELF, METRIC_RUN_SUFFIX, METRIC_VERSION
from .fission_multiplier import (
    DEFAULT_FISSION_PARAMETER_VERSION,
    FissionMultiplierParameters,
    parameters_from_database,
)
from .policy import annotate_execution
from .reporting import (
    REPORT_RUN_SUFFIX,
    REPORT_VERSION,
    write_agency_workbooks,
    write_workbook,
)
from .rules import (
    POLICY_RUN_SUFFIX,
    POLICY_VERSION,
    RuleConfig,
    evaluate_rules,
)
from .repository import (
    FINAL_STATUSES,
    create_or_load_run,
    load_agency_deliveries,
    load_fission_parameter_release,
    mark_failed,
    mark_published,
    replace_run_results,
)


SHANGHAI = ZoneInfo("Asia/Shanghai")
logger = logging.getLogger("auto_put_ad_mini.roi_control")


def _redact_agency_webhooks(
    error: Exception,
    config: AgencyWebhookConfig,
) -> str:
    message = str(error)
    for webhook_url in (config.webhooks or {}).values():
        message = message.replace(webhook_url, "<redacted>")
    return message


def _internal_source_revision(config: RoiConfig, now: datetime) -> str:
    if config.internal_test_dedup_enabled:
        return "internal_test"
    return f"it_{now.strftime('%H%M%S%f')}"


def _build_internal_reports(
    *,
    agency_reports: list[dict[str, object]],
) -> list[dict[str, object]]:
    reports = [
        {
            **report,
            "title": f"内部测试_{Path(str(report['report'])).stem}",
        }
        for report in agency_reports
        if agency_route_key(str(report["agency_name"])) == "自动化投放"
    ]
    if len(reports) != 1:
        raise RuntimeError(
            "Internal ROI test requires exactly one 自动化投放 agency report"
        )
    return reports


def _internal_webhook_config(
    reports: list[dict[str, object]],
    webhook_url: str,
) -> AgencyWebhookConfig:
    return AgencyWebhookConfig(
        enabled=True,
        webhooks={
            agency_route_key(str(report["agency_name"])): webhook_url
            for report in reports
        },
    )


def _annotate_current_creative_status(
    rows,
    tencent: TencentClient | None = None,
):
    """Read current Tencent status only for creative-level stop decisions."""

    result = rows.copy()
    result["当前创意状态"] = ""
    mask = result["entity_type"].eq(ENTITY_SELF) & result["动作"].eq("关停")
    if not mask.any():
        return result

    client = tencent or TencentClient()
    cache: dict[tuple[int, int], str] = {}
    try:
        for index, row in result.loc[mask].iterrows():
            try:
                account_id = int(row["账号id"])
                creative_id = int(row["创意id"])
            except (TypeError, ValueError):
                result.at[index, "当前创意状态"] = "读取失败"
                continue
            key = (account_id, creative_id)
            if key not in cache:
                try:
                    creative = client.get_dynamic_creative(account_id, creative_id)
                    status = str(creative.get("configured_status") or "")
                    cache[key] = (
                        "正常"
                        if status == ACTIVE_STATUS
                        else "已停止" if status else "读取失败"
                    )
                except Exception as exc:
                    logger.warning(
                        "Failed to read creative status account=%s creative=%s: %s",
                        account_id,
                        creative_id,
                        exc,
                    )
                    cache[key] = "读取失败"
            result.at[index, "当前创意状态"] = cache[key]
    finally:
        if tencent is None:
            client.session.close()
    return result


def _rule_config(config: RoiConfig) -> RuleConfig:
    return RuleConfig(
        self_stop_min_age=config.self_stop_min_age,
        self_up_min_age=config.self_up_min_age,
        self_min_daily_uv=config.self_min_daily_uv,
        partner_min_daily_uv=config.partner_min_daily_uv,
        observe_min_latest_uv=config.observe_min_latest_uv,
        one_day_min_uv=config.one_day_min_uv,
        one_day_hard_stop_roi=config.one_day_hard_stop_roi,
        one_day_stop_quantile=config.one_day_stop_quantile,
        self_stop_cost_soft_lower_ratio=config.self_stop_cost_soft_lower_ratio,
        self_stop_cost_target_ratio=config.self_stop_cost_target_ratio,
        self_stop_cost_soft_upper_ratio=config.self_stop_cost_soft_upper_ratio,
        self_stop_cost_hard_cap_ratio=config.self_stop_cost_hard_cap_ratio,
        self_stop_three_day_share=config.self_stop_three_day_share,
        self_stop_one_day_share=config.self_stop_one_day_share,
        stop_quantile=config.stop_quantile,
        up_quantile=config.up_quantile,
    )


def _dates(start_date: str) -> list[str]:
    start = datetime.strptime(start_date, "%Y%m%d")
    return [
        (start + timedelta(days=offset)).strftime("%Y%m%d")
        for offset in range(3)
    ]


def _run_identity(
    end_date: str,
    fission_parameters: FissionMultiplierParameters,
    source_revision: str | None = None,
) -> tuple[str, str]:
    if source_revision and not re.fullmatch(
        r"[a-z0-9][a-z0-9_-]{0,63}", source_revision
    ):
        raise ValueError(
            "source_revision must use lowercase letters, numbers, '_' or '-'"
        )
    release = fission_parameters.release
    run_suffixes = [release.run_suffix]
    run_key_versions = [release.version]
    run_id = (
        f"roi_{end_date}_{METRIC_RUN_SUFFIX}_{POLICY_RUN_SUFFIX}_"
        f"{'_'.join(run_suffixes)}_{REPORT_RUN_SUFFIX}"
    )
    run_key = (
        f"{METRIC_VERSION}:{POLICY_VERSION}:{':'.join(run_key_versions)}:"
        f"{REPORT_VERSION}:{end_date}"
    )
    if source_revision:
        run_id = f"{run_id}_{source_revision}"
        revision_hash = hashlib.sha256(source_revision.encode("utf-8")).hexdigest()
        run_key = f"{run_key}:sr:{revision_hash[:16]}"
    if len(run_id) > 64:
        raise ValueError("ROI run_id exceeds database limit")
    if len(run_key) > 128:
        raise ValueError("ROI run_key exceeds database limit")
    return run_id, run_key


def run_daily_roi(
    *,
    requested_end_date: str | None = None,
    output_dir: Path,
    send_feishu: bool,
    now: datetime | None = None,
    source_revision: str | None = None,
    internal_test: bool = False,
) -> dict[str, Any]:
    """Compute, snapshot, report, and optionally publish one ROI batch."""

    config = RoiConfig.from_env()
    agency_webhook_config = AgencyWebhookConfig.from_env()
    if internal_test and send_feishu:
        raise ValueError("internal_test cannot publish formal Feishu notifications")
    if internal_test and not (agency_webhook_config.webhooks or {}).get("内部"):
        raise ValueError("Internal ROI test requires agency webhook route: 内部")
    effective_now = now or datetime.now(SHANGHAI)
    if effective_now.tzinfo is None:
        effective_now = effective_now.replace(tzinfo=SHANGHAI)
    if internal_test:
        if source_revision:
            raise ValueError("internal_test manages source_revision automatically")
        source_revision = _internal_source_revision(config, effective_now)

    client = ODPSClient(project=os.getenv("ODPS_PROJECT", "loghubods"))
    end_date = resolve_end_date(
        client,
        requested_end_date,
        now=effective_now,
    )
    start_date, end_date = date_window(end_date)
    expected_dates = _dates(start_date)

    initialize_schema()
    fission_version = os.getenv(
        "ROI_FISSION_PARAMETER_VERSION",
        DEFAULT_FISSION_PARAMETER_VERSION,
    )
    fission_parameters = parameters_from_database(
        *load_fission_parameter_release(fission_version)
    )
    run_config = config.snapshot()
    run_config["fission_multiplier"] = fission_parameters.snapshot()
    run_config["report_version"] = REPORT_VERSION
    run_config["agency_webhook"] = agency_webhook_config.snapshot()
    run_config["internal_test"] = internal_test
    if source_revision:
        run_config["source_revision"] = source_revision
    run_id, run_key = _run_identity(
        end_date,
        fission_parameters,
        source_revision,
    )
    run = create_or_load_run(
        {
            "run_id": run_id,
            "run_key": run_key,
            "metric_version": METRIC_VERSION,
            "policy_version": POLICY_VERSION,
            "fission_parameter_version": fission_parameters.release.version,
            "fission_cohort_date": datetime.strptime(
                fission_parameters.release.cohort_date, "%Y%m%d"
            ).date(),
            "start_date": datetime.strptime(start_date, "%Y%m%d").date(),
            "end_date": datetime.strptime(end_date, "%Y%m%d").date(),
            "config": run_config,
        }
    )
    reusable_statuses = FINAL_STATUSES | {"PENDING_APPROVAL", "EXECUTING"}
    publish_requested = send_feishu or internal_test
    if run.get("status") in reusable_statuses or (
        run.get("status") == "COMPUTED" and not publish_requested
    ):
        logger.info("Reuse ROI run=%s status=%s", run_id, run.get("status"))
        reused_result: dict[str, Any] = {
            "run_id": run_id,
            "status": run.get("status"),
            "reused": True,
            "sheet_url": run.get("sheet_url"),
        }
        if send_feishu and agency_webhook_config.enabled:
            stored_deliveries = load_agency_deliveries(run_id)
            if stored_deliveries:
                retry_reports = [
                    {
                        "agency_name": row["agency_name"],
                        "report_version": row["agency_report_version"],
                        "report": row["file_path"],
                        "creative_rows": row.get("creative_rows") or 0,
                        "ad_rows": row.get("ad_rows") or 0,
                    }
                    for row in stored_deliveries
                ]
                publisher = RoiFeishuPublisher()
                try:
                    try:
                        reused_result["agency_deliveries"] = publish_agency_reports(
                            run_id=run_id,
                            reports=retry_reports,
                            config=agency_webhook_config,
                            publisher=publisher,
                            now=now,
                        )
                    except Exception as exc:
                        safe_error = _redact_agency_webhooks(
                            exc,
                            agency_webhook_config,
                        )
                        logger.error("Agency ROI retry failed: %s", safe_error)
                        reused_result["agency_delivery_error"] = safe_error
                finally:
                    publisher.close()
        return reused_result

    try:
        logger.info(
            "ROI %s loading ODPS daily data %s..%s fission_parameter=%s",
            run_id,
            start_date,
            end_date,
            fission_parameters.release.version,
        )
        daily = fetch_daily_data(client, start_date, end_date)
        ad_age = fetch_ad_age(client, end_date)
        _, thresholds, summary = evaluate_rules(
            daily,
            expected_dates,
            ad_age,
            _rule_config(config),
            fission_parameters=fission_parameters,
        )
        managed_ids = (
            set()
            if internal_test
            else {
                int(row["account_id"])
                for row in load_managed_accounts()
            }
        )
        annotated, snapshots, actions = annotate_execution(
            summary,
            managed_ids,
            run_id,
        )
        annotated = _annotate_current_creative_status(annotated)
        fission_match_summary = (
            summary.groupby(
                ["entity_type", "传播裂变系数匹配层级"],
                as_index=False,
                dropna=False,
            )
            .size()
            .rename(
                columns={
                    "entity_type": "实体类型",
                    "传播裂变系数匹配层级": "匹配层级",
                    "size": "实体数",
                }
            )
        )
        channel_totals = fission_match_summary.groupby("实体类型")[
            "实体数"
        ].transform("sum")
        fission_match_summary["渠道实体数"] = channel_totals
        fission_match_summary["匹配率"] = (
            fission_match_summary["实体数"] / channel_totals
        )
        fission_match_summary["参数版本"] = fission_parameters.release.version
        recommendation_rows = annotated[annotated["动作"].ne("")].copy()
        report_rows = annotated[
            annotated["阈值样本状态"].isin(
                [
                    "进入三日渠道独立阈值样本池",
                    "广告级三日合格_不进入阈值样本池",
                    "单日补充决策_昨日UV>200",
                    "补充观察_昨日UV>200",
                ]
            )
        ].copy()
        for row in snapshots:
            row["run_id"] = run_id
        for row in actions:
            row["run_id"] = run_id
        threshold_record = {
            "统计窗口": f"{start_date} 至 {end_date}",
            "渠道独立关停线与小程序成本预算": thresholds.to_dict("records"),
        }
        replace_run_results(
            run_id,
            snapshots=snapshots,
            actions=actions,
            thresholds=threshold_record,
        )

        output_dir.mkdir(parents=True, exist_ok=True)
        batch_name = f"ROI调控_{start_date}-{end_date}"
        if source_revision:
            batch_name = f"{batch_name}_{source_revision}"
        output_path = output_dir / f"{batch_name}.xlsx"
        if not internal_test:
            write_workbook(
                report_rows,
                thresholds,
                expected_dates,
                output_path,
                run_config,
                fission_match_summary,
            )
        agency_report_date = effective_now.strftime("%Y%m%d")
        agency_reports = write_agency_workbooks(
            report_rows,
            output_dir / f"{agency_report_date}_调控建议",
            agency_report_date,
            agency_names={"自动化投放"} if internal_test else None,
        )
        internal_reports = (
            _build_internal_reports(agency_reports=agency_reports)
            if internal_test
            else []
        )
        result_report = (
            str(internal_reports[0]["report"])
            if internal_test
            else str(output_path)
        )

        result: dict[str, Any] = {
            "run_id": run_id,
            "batch_name": batch_name,
            "status": "COMPUTED",
            "reused": False,
            "start_date": start_date,
            "end_date": end_date,
            "source_revision": source_revision,
            "entity_count": len(snapshots),
            "candidate_count": len(recommendation_rows),
            "actionable_count": len(actions),
            "thresholds": threshold_record,
            "fission_multiplier_matches": fission_match_summary.to_dict(
                "records"
            ),
            "report": result_report,
            "agency_reports": agency_reports,
        }
        if not publish_requested:
            return result

        if internal_test:
            internal_url = (agency_webhook_config.webhooks or {})["内部"]
            internal_config = _internal_webhook_config(
                internal_reports,
                internal_url,
            )
            publisher = RoiFeishuPublisher(require_chat_ids=False)
            try:
                deliveries = publish_agency_reports(
                    run_id=run_id,
                    reports=internal_reports,
                    config=internal_config,
                    publisher=publisher,
                    now=effective_now,
                )
            finally:
                publisher.close()
            result["internal_deliveries"] = deliveries
            failed = [row for row in deliveries if row.get("status") != "SENT"]
            if failed:
                raise RuntimeError(
                    f"Internal ROI test delivery failed for {len(failed)} report(s)"
                )
            main_delivery = next(
                row for row in deliveries if row["agency_name"] == "自动化投放"
            )
            mark_published(
                run_id,
                sheet_token=str(main_delivery["sheet_token"]),
                sheet_url=str(main_delivery["sheet_url"]),
                message_id="internal-webhook",
                expires_at=None,
                requires_approval=False,
            )
            result.update(
                {
                    "status": "COMPLETED",
                    "sheet_url": main_delivery["sheet_url"],
                }
            )
            return result

        counts = recommendation_rows.groupby("动作").size().to_dict()
        summary_text = (
            f"统计窗口：{start_date} - {end_date}\n"
            f"关停建议：{counts.get('关停', 0)}\n"
            f"观察：{counts.get('观察', 0)}\n"
            f"可执行动作：{len(actions)}\n"
            f"审批有效期：发送后 {config.approval_ttl_minutes} 分钟"
        )
        publisher = RoiFeishuPublisher()
        try:
            published = publisher.publish(
                output_path,
                run_id=run_id,
                batch_name=batch_name,
                summary=summary_text,
                requires_approval=bool(actions),
            )
            expires_at = (
                effective_now
                + timedelta(minutes=config.approval_ttl_minutes)
                if actions
                else None
            )
            mark_published(
                run_id,
                sheet_token=published["sheet_token"],
                sheet_url=published["url"],
                message_id=published["message_id"],
                expires_at=expires_at,
                requires_approval=bool(actions),
            )
            try:
                result["agency_deliveries"] = publish_agency_reports(
                    run_id=run_id,
                    reports=agency_reports,
                    config=agency_webhook_config,
                    publisher=publisher,
                    now=now,
                )
            except Exception as exc:
                safe_error = _redact_agency_webhooks(
                    exc,
                    agency_webhook_config,
                )
                logger.error("Agency ROI delivery phase failed: %s", safe_error)
                result["agency_delivery_error"] = safe_error
        finally:
            publisher.close()
        result.update(
            {
                "status": "PENDING_APPROVAL" if actions else "COMPLETED",
                "sheet_url": published["url"],
                "expires_at": expires_at.isoformat() if expires_at else None,
            }
        )
        return result
    except Exception as exc:
        mark_failed(run_id, str(exc))
        raise
