from __future__ import annotations

import json
import math
import os
from collections import Counter, defaultdict
from dataclasses import dataclass
from datetime import timedelta
from pathlib import Path
from typing import Any

import requests

from ..config import ClientConfig, client_dir
from ..google_ads import get_google_ads_client, run_query
from ..history import append_change_markdown, append_history, now_local
from ..table import print_table
from .pla_cl1_sync import (
    fetch_adspro_products,
    is_shared_catch_all_campaign,
    normalize_variant,
    parse_allowed_labels,
    parse_campaign_variant,
)


TASK_ID = "split_pla_cl1_bestsellers"
TASK_NAME = "Podzial produktow CL1 na bestsellery i catch_all"


DEFAULT_SETTINGS = {
    "metric_days": 180,
    "recent_days": 30,
    "core_ratio": 0.20,
    "min_core_products": 20,
    "max_core_products": 80,
    "min_catch_all_products": 10,
    "rank_buffer": 3,
    "catch_all_label": "catch_all",
    "min_cost_for_roas_score": 20.0,
    "min_cost_without_conversion": 50.0,
    "max_cost_penalty": 25.0,
    "protected_cl4_labels": ["paused"],
}


@dataclass
class ProductCl1BestsellerSplitPlan:
    metric_window: dict
    recent_window: dict
    settings: dict
    campaigns: list[dict]
    segment_summaries: list[dict]
    changes: list[dict]
    warnings: list[str]

    def to_dict(self) -> dict:
        return {
            "task": TASK_ID,
            "task_name": TASK_NAME,
            "metric_window": self.metric_window,
            "recent_window": self.recent_window,
            "settings": self.settings,
            "campaigns": self.campaigns,
            "segment_summaries": self.segment_summaries,
            "changes": self.changes,
            "warnings": self.warnings,
        }

    @classmethod
    def from_dict(cls, data: dict) -> "ProductCl1BestsellerSplitPlan":
        return cls(
            metric_window=data.get("metric_window", {}),
            recent_window=data.get("recent_window", {}),
            settings=data.get("settings", {}),
            campaigns=data.get("campaigns", []),
            segment_summaries=data.get("segment_summaries", []),
            changes=data.get("changes", []),
            warnings=data.get("warnings", []),
        )


def safe_int(value: Any, default: int = 0) -> int:
    try:
        return int(value)
    except (TypeError, ValueError):
        return default


def safe_float(value: Any, default: float = 0.0) -> float:
    try:
        return float(value)
    except (TypeError, ValueError):
        return default


def format_decimal(value: Any, digits: int = 2) -> str:
    return f"{safe_float(value):.{digits}f}"


def split_settings(client_config: ClientConfig, global_rules: dict) -> dict:
    settings = dict(DEFAULT_SETTINGS)
    rules = client_config.effective_rules(global_rules, TASK_ID)
    for key in DEFAULT_SETTINGS:
        if key in rules:
            settings[key] = rules[key]

    settings["metric_days"] = max(30, safe_int(settings["metric_days"], 180))
    settings["recent_days"] = max(7, safe_int(settings["recent_days"], 30))
    settings["core_ratio"] = min(max(safe_float(settings["core_ratio"], 0.20), 0.05), 0.80)
    settings["min_core_products"] = max(1, safe_int(settings["min_core_products"], 20))
    settings["max_core_products"] = max(settings["min_core_products"], safe_int(settings["max_core_products"], 80))
    settings["min_catch_all_products"] = max(1, safe_int(settings["min_catch_all_products"], 10))
    settings["rank_buffer"] = max(0, safe_int(settings["rank_buffer"], 3))
    settings["catch_all_label"] = normalize_variant(str(settings["catch_all_label"] or "catch_all"))
    settings["min_cost_for_roas_score"] = max(0.0, safe_float(settings["min_cost_for_roas_score"], 20.0))
    settings["min_cost_without_conversion"] = max(0.0, safe_float(settings["min_cost_without_conversion"], 50.0))
    settings["max_cost_penalty"] = max(0.0, safe_float(settings["max_cost_penalty"], 25.0))
    protected = settings.get("protected_cl4_labels") or ["paused"]
    if not isinstance(protected, list):
        protected = ["paused"]
    settings["protected_cl4_labels"] = sorted({normalize_variant(str(item)) for item in protected if str(item).strip()})
    return settings


def adspro_credentials(client_config: ClientConfig) -> tuple[str, str, str]:
    api_url = os.environ.get("ADSPRO_API_URL")
    api_key = os.environ.get("ADSPRO_API_KEY")
    if not api_url or not api_key:
        raise RuntimeError("Brak ADSPRO_API_URL lub ADSPRO_API_KEY w .env.")
    if not client_config.adspro_client_id:
        raise RuntimeError(f"Brak adspro_client_id dla {client_config.domain} w config/clients.toml.")
    return api_url, api_key, client_config.adspro_client_id


def set_adspro_cl4(
    api_url: str,
    api_key: str,
    adspro_client_id: str,
    offer_id: str,
    value: str,
) -> dict:
    response = requests.post(
        api_url,
        data={
            "action": "product_custom_label_4_set",
            "api_key": api_key,
            "client_id": adspro_client_id,
            "offer_id": offer_id,
            "custom_label_4": value,
        },
        timeout=30,
    )
    response.raise_for_status()
    response.encoding = "utf-8"
    try:
        data = response.json()
    except ValueError:
        data = {"result": "error", "message": response.text[:500]}
    if data.get("result") == "error":
        raise RuntimeError(data.get("message") or "adsPRO zwrocil blad.")
    return data


def fetch_pla_cl1_campaigns(client_config: ClientConfig) -> list[dict]:
    google_client = get_google_ads_client(use_proto_plus=True)
    rows = run_query(
        google_client,
        client_config.safe_customer_id,
        """
        SELECT campaign.id, campaign.name, campaign.status
        FROM campaign
        WHERE campaign.name LIKE '%PLA_CL1%'
          AND campaign.status = 'ENABLED'
        """,
    )
    campaigns = []
    for row in rows:
        if is_shared_catch_all_campaign(row.campaign.name):
            continue
        campaigns.append(
            {
                "id": str(row.campaign.id),
                "name": row.campaign.name,
                "status": row.campaign.status.name,
                "allowed": sorted(parse_allowed_labels(row.campaign.name)),
                "variant": parse_campaign_variant(row.campaign.name),
            }
        )
    return campaigns


def product_metrics_row() -> dict:
    return {
        "impressions": 0,
        "clicks": 0,
        "cost": 0.0,
        "conversions": 0.0,
        "conversion_value": 0.0,
        "roas": 0.0,
    }


def fetch_product_metrics(client_config: ClientConfig, start_date: str, end_date: str) -> dict[str, dict]:
    google_client = get_google_ads_client(use_proto_plus=True)
    rows = run_query(
        google_client,
        client_config.safe_customer_id,
        f"""
        SELECT
          segments.product_item_id,
          metrics.impressions,
          metrics.clicks,
          metrics.cost_micros,
          metrics.conversions,
          metrics.conversions_value
        FROM shopping_performance_view
        WHERE segments.date BETWEEN '{start_date}' AND '{end_date}'
        """,
        timeout=300.0,
    )
    metrics: dict[str, dict] = {}
    for row in rows:
        offer_id = str(row.segments.product_item_id or "").strip()
        if not offer_id:
            continue
        record = metrics.setdefault(offer_id, product_metrics_row())
        record["impressions"] += safe_int(row.metrics.impressions)
        record["clicks"] += safe_int(row.metrics.clicks)
        record["cost"] += safe_float(row.metrics.cost_micros) / 1_000_000
        record["conversions"] += safe_float(row.metrics.conversions)
        record["conversion_value"] += safe_float(row.metrics.conversions_value)

    for record in metrics.values():
        record["cost"] = round(record["cost"], 2)
        record["conversions"] = round(record["conversions"], 4)
        record["conversion_value"] = round(record["conversion_value"], 2)
        record["roas"] = round(record["conversion_value"] / record["cost"], 4) if record["cost"] else 0.0
    return metrics


def score_percentiles(rows: list[dict], metric_key: str, score_key: str) -> None:
    positive_values = sorted({safe_float(row.get(metric_key)) for row in rows if safe_float(row.get(metric_key)) > 0})
    if not positive_values:
        for row in rows:
            row[score_key] = 0.0
        return
    if len(positive_values) == 1:
        only_value = positive_values[0]
        for row in rows:
            row[score_key] = 100.0 if safe_float(row.get(metric_key)) == only_value else 0.0
        return
    ranks = {value: index for index, value in enumerate(positive_values)}
    max_rank = len(positive_values) - 1
    for row in rows:
        value = safe_float(row.get(metric_key))
        row[score_key] = round(100.0 * ranks[value] / max_rank, 2) if value > 0 else 0.0


def add_scores(rows: list[dict], settings: dict) -> None:
    max_cost = max((safe_float(row.get("cost_180d")) for row in rows), default=0.0)
    for row in rows:
        if safe_float(row.get("cost_180d")) >= settings["min_cost_for_roas_score"]:
            row["roas_score_input"] = safe_float(row.get("roas_180d"))
        else:
            row["roas_score_input"] = 0.0

    score_percentiles(rows, "conversion_value_180d", "conversion_value_180d_score")
    score_percentiles(rows, "conversions_180d", "conversions_180d_score")
    score_percentiles(rows, "conversion_value_30d", "conversion_value_30d_score")
    score_percentiles(rows, "roas_score_input", "roas_180d_score")

    for row in rows:
        penalty = 0.0
        if (
            safe_float(row.get("conversions_180d")) <= 0
            and safe_float(row.get("cost_180d")) >= settings["min_cost_without_conversion"]
            and max_cost > 0
        ):
            penalty = min(
                settings["max_cost_penalty"],
                10.0 + (settings["max_cost_penalty"] - 10.0) * safe_float(row.get("cost_180d")) / max_cost,
            )
        row["cost_without_conversion_penalty"] = round(penalty, 2)
        row["score"] = round(
            0.40 * row["conversion_value_180d_score"]
            + 0.25 * row["conversions_180d_score"]
            + 0.20 * row["conversion_value_30d_score"]
            + 0.15 * row["roas_180d_score"]
            - penalty,
            2,
        )


def core_count_for_segment(products_count: int, settings: dict) -> int:
    by_ratio = math.ceil(products_count * settings["core_ratio"])
    count = max(settings["min_core_products"], by_ratio)
    count = min(settings["max_core_products"], count)
    return min(products_count, count)


def has_any_data(row: dict) -> bool:
    return any(
        safe_float(row.get(key)) > 0
        for key in (
            "impressions_180d",
            "clicks_180d",
            "cost_180d",
            "conversions_180d",
            "conversion_value_180d",
        )
    )


def has_sales_signal(row: dict) -> bool:
    return safe_float(row.get("conversions_180d")) > 0 or safe_float(row.get("conversion_value_180d")) > 0


def build_rank_rows(
    products: list[dict],
    metrics_180d: dict[str, dict],
    metrics_30d: dict[str, dict],
    settings: dict,
) -> tuple[dict[str, list[dict]], Counter]:
    catch_all = settings["catch_all_label"]
    protected = set(settings["protected_cl4_labels"])
    allowed = {"", catch_all}
    skipped: Counter = Counter()
    by_cl1: dict[str, list[dict]] = defaultdict(list)

    for product in products:
        offer_id = str(product.get("offer_id") or "").strip()
        cl1 = str(product.get("custom_label_1") or "").strip()
        current_cl4 = str(product.get("custom_label_4") or "").strip()
        current_norm = normalize_variant(current_cl4)
        if not offer_id or not cl1:
            skipped["missing_offer_or_cl1"] += 1
            continue
        if current_norm in protected:
            skipped["protected_cl4"] += 1
            continue
        if current_norm not in allowed:
            skipped["other_cl4"] += 1
            continue

        m180 = metrics_180d.get(offer_id, product_metrics_row())
        m30 = metrics_30d.get(offer_id, product_metrics_row())
        row = {
            "offer_id": offer_id,
            "title": product.get("title", "") or "",
            "custom_label_1": cl1,
            "current_custom_label_4": current_cl4,
            "current_custom_label_4_normalized": current_norm,
            "impressions_180d": safe_int(m180.get("impressions")),
            "clicks_180d": safe_int(m180.get("clicks")),
            "cost_180d": round(safe_float(m180.get("cost")), 2),
            "conversions_180d": round(safe_float(m180.get("conversions")), 4),
            "conversion_value_180d": round(safe_float(m180.get("conversion_value")), 2),
            "roas_180d": round(safe_float(m180.get("roas")), 4),
            "impressions_30d": safe_int(m30.get("impressions")),
            "clicks_30d": safe_int(m30.get("clicks")),
            "cost_30d": round(safe_float(m30.get("cost")), 2),
            "conversions_30d": round(safe_float(m30.get("conversions")), 4),
            "conversion_value_30d": round(safe_float(m30.get("conversion_value")), 2),
            "roas_30d": round(safe_float(m30.get("roas")), 4),
        }
        by_cl1[cl1].append(row)
    return by_cl1, skipped


def campaign_variants_by_cl1(campaigns: list[dict]) -> dict[str, set[str]]:
    variants: dict[str, set[str]] = defaultdict(set)
    for campaign in campaigns:
        for label in campaign.get("allowed", []):
            variants[label].add(normalize_variant(campaign.get("variant", "")))
    return variants


def ranked_sort_key(row: dict) -> tuple:
    current_priority = 0 if row.get("current_custom_label_4_normalized") == "" else 1
    return (
        -safe_float(row.get("score")),
        -safe_float(row.get("conversion_value_180d")),
        -safe_float(row.get("conversions_180d")),
        -safe_float(row.get("conversion_value_30d")),
        -safe_float(row.get("roas_180d")),
        current_priority,
        str(row.get("title", "")).lower(),
        str(row.get("offer_id", "")),
    )


def build_segment_plan(
    cl1: str,
    rows: list[dict],
    variants: set[str],
    settings: dict,
) -> tuple[dict, list[dict]]:
    catch_all = settings["catch_all_label"]
    summary = {
        "custom_label_1": cl1,
        "status": "planned",
        "reason": "",
        "managed_products": len(rows),
        "products_with_any_data": sum(1 for row in rows if has_any_data(row)),
        "products_with_sales_signal": sum(1 for row in rows if has_sales_signal(row)),
        "core_rank_target": 0,
        "rank_buffer": settings["rank_buffer"],
        "final_core_products": 0,
        "final_catch_all_products": 0,
        "planned_set_catch_all": 0,
        "planned_clear_catch_all": 0,
    }

    if "" not in variants:
        summary["status"] = "skipped"
        summary["reason"] = "brak aktywnej kampanii bazowej dla tego CL1"
        return summary, []

    core_count = core_count_for_segment(len(rows), settings)
    catch_all_count = len(rows) - core_count
    summary["core_rank_target"] = core_count
    if len(rows) < settings["min_core_products"] + settings["min_catch_all_products"]:
        summary["status"] = "skipped"
        summary["reason"] = "za malo produktow do bezpiecznego podzialu"
        return summary, []
    if catch_all_count < settings["min_catch_all_products"]:
        summary["status"] = "skipped"
        summary["reason"] = "po podziale wariant catch_all bylby za maly"
        return summary, []
    if summary["products_with_sales_signal"] <= 0:
        summary["status"] = "skipped"
        summary["reason"] = "brak sprzedazy albo wartosci konwersji w oknie 180 dni"
        return summary, []

    add_scores(rows, settings)
    ranked = sorted(rows, key=ranked_sort_key)
    changes = []
    final_core = 0
    final_catch_all = 0
    buffer = settings["rank_buffer"]
    max_final_core = len(rows) - settings["min_catch_all_products"]
    core_promotion_limit = min(max_final_core, max(settings["min_core_products"], core_count - buffer))
    catch_all_move_after = min(max_final_core, core_count + buffer)
    for rank, row in enumerate(ranked, 1):
        current = row["current_custom_label_4_normalized"]
        if current == catch_all:
            target = "" if rank <= core_promotion_limit else catch_all
            reason = f"produkt wraca do glownej kampanii: rank {rank} miesci sie w stabilnym rdzeniu TOP {core_promotion_limit}"
        else:
            target = catch_all if rank > catch_all_move_after else ""
            reason = f"produkt przechodzi do catch_all: rank {rank} poza stabilnym progiem TOP {catch_all_move_after}"

        if target == "":
            final_core += 1
        else:
            final_catch_all += 1

        if target == current:
            continue
        change = {
            "offer_id": row["offer_id"],
            "title": row["title"],
            "custom_label_1": cl1,
            "rank": rank,
            "score": row["score"],
            "current_custom_label_4": row["current_custom_label_4"],
            "target_custom_label_4": target,
            "action": "clear_catch_all" if target == "" else "set_catch_all",
            "reason": reason,
            "conversions_180d": row["conversions_180d"],
            "conversion_value_180d": row["conversion_value_180d"],
            "cost_180d": row["cost_180d"],
            "roas_180d": row["roas_180d"],
            "conversion_value_30d": row["conversion_value_30d"],
            "cost_without_conversion_penalty": row["cost_without_conversion_penalty"],
        }
        changes.append(change)

    summary["final_core_products"] = final_core
    summary["final_catch_all_products"] = final_catch_all
    summary["planned_set_catch_all"] = sum(1 for row in changes if row["action"] == "set_catch_all")
    summary["planned_clear_catch_all"] = sum(1 for row in changes if row["action"] == "clear_catch_all")
    return summary, changes


def build_product_cl1_bestseller_split_plan(
    client_config: ClientConfig,
    global_rules: dict,
) -> ProductCl1BestsellerSplitPlan:
    settings = split_settings(client_config, global_rules)
    ended_at = now_local().date() - timedelta(days=1)
    metric_started_at = ended_at - timedelta(days=settings["metric_days"] - 1)
    recent_started_at = ended_at - timedelta(days=settings["recent_days"] - 1)
    metric_window = {"start": metric_started_at.isoformat(), "end": ended_at.isoformat(), "days": settings["metric_days"]}
    recent_window = {"start": recent_started_at.isoformat(), "end": ended_at.isoformat(), "days": settings["recent_days"]}

    campaigns = fetch_pla_cl1_campaigns(client_config)
    variants_by_cl1 = campaign_variants_by_cl1(campaigns)
    segments = sorted(variants_by_cl1)
    warnings: list[str] = []
    if not segments:
        warnings.append("Nie znaleziono aktywnych kampanii PLA_CL1 z ktorych mozna odczytac CL1.")
        return ProductCl1BestsellerSplitPlan(metric_window, recent_window, settings, campaigns, [], [], warnings)

    metrics_180d = fetch_product_metrics(client_config, metric_window["start"], metric_window["end"])
    metrics_30d = fetch_product_metrics(client_config, recent_window["start"], recent_window["end"])
    products = fetch_adspro_products(client_config, segments)
    by_cl1, skipped = build_rank_rows(products, metrics_180d, metrics_30d, settings)

    if skipped["protected_cl4"]:
        warnings.append(f"Pominieto produkty z chronionym CL4: {skipped['protected_cl4']}.")
    if skipped["other_cl4"]:
        warnings.append(f"Pominieto produkty z innym CL4 niz puste/catch_all: {skipped['other_cl4']}.")
    if skipped["missing_offer_or_cl1"]:
        warnings.append(f"Pominieto produkty bez offer_id albo CL1: {skipped['missing_offer_or_cl1']}.")

    segment_summaries = []
    changes = []
    for cl1 in segments:
        summary, segment_changes = build_segment_plan(cl1, by_cl1.get(cl1, []), variants_by_cl1.get(cl1, set()), settings)
        segment_summaries.append(summary)
        changes.extend(segment_changes)

    changes.sort(key=lambda item: (item["custom_label_1"], item["rank"], item["offer_id"]))
    return ProductCl1BestsellerSplitPlan(
        metric_window=metric_window,
        recent_window=recent_window,
        settings=settings,
        campaigns=campaigns,
        segment_summaries=segment_summaries,
        changes=changes,
        warnings=warnings,
    )


def save_plan(domain: str, plan: ProductCl1BestsellerSplitPlan) -> tuple[Path, Path]:
    ts = now_local()
    base = client_dir(domain) / "plans"
    base.mkdir(parents=True, exist_ok=True)
    stem = f"{ts.strftime('%Y-%m-%d_%H-%M-%S')}_{TASK_ID}"
    json_path = base / f"{stem}.json"
    md_path = base / f"{stem}.md"
    payload = {"created_at": ts.isoformat(timespec="seconds"), "client": domain, **plan.to_dict()}
    json_path.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")

    lines = [
        f"# Plan: {TASK_NAME}",
        "",
        f"Klient: {domain}",
        f"Utworzono: {ts.isoformat(timespec='seconds')}",
        f"Dane glowne: {plan.metric_window.get('start')} - {plan.metric_window.get('end')}",
        f"Dane swiezosci: {plan.recent_window.get('start')} - {plan.recent_window.get('end')}",
        "",
        "## Podsumowanie",
        "",
        f"- Segmenty CL1: {len(plan.segment_summaries)}",
        f"- Zmiany CL4 do wdrozenia: {len(plan.changes)}",
        f"- Do catch_all: {sum(1 for row in plan.changes if row.get('action') == 'set_catch_all')}",
        f"- Do glownej kampanii: {sum(1 for row in plan.changes if row.get('action') == 'clear_catch_all')}",
        "",
    ]
    if plan.warnings:
        lines.extend(["## Uwagi", ""])
        lines.extend(f"- {warning}" for warning in plan.warnings)
        lines.append("")

    if plan.segment_summaries:
        lines.extend(
            [
                "## Podsumowanie po CL1",
                "",
                "| CL1 | Status | Produkty | Rdzen rankingu | Docelowo glowna | Docelowo catch_all | Do catch_all | Do glownej | Powod |",
                "| --- | --- | ---: | ---: | ---: | ---: | ---: | ---: | --- |",
            ]
        )
        for row in plan.segment_summaries:
            lines.append(
                f"| {row.get('custom_label_1', '')} | {row.get('status', '')} | "
                f"{row.get('managed_products', 0)} | {row.get('core_rank_target', 0)} | "
                f"{row.get('final_core_products', 0)} | {row.get('final_catch_all_products', 0)} | "
                f"{row.get('planned_set_catch_all', 0)} | {row.get('planned_clear_catch_all', 0)} | "
                f"{row.get('reason', '')} |"
            )
        lines.append("")

    if plan.changes:
        lines.extend(
            [
                "## Zmiany do wdrozenia",
                "",
                "| CL1 | Rank | Produkt | Obecne CL4 | Docelowe CL4 | Score | Wartosc 180d | Koszt 180d | ROAS 180d | Powod |",
                "| --- | ---: | --- | --- | --- | ---: | ---: | ---: | ---: | --- |",
            ]
        )
        for row in plan.changes:
            lines.append(
                f"| {row.get('custom_label_1', '')} | {row.get('rank', '')} | "
                f"{row.get('offer_id', '')} | {row.get('current_custom_label_4', '')} | "
                f"{row.get('target_custom_label_4', '')} | {format_decimal(row.get('score'))} | "
                f"{format_decimal(row.get('conversion_value_180d'))} | {format_decimal(row.get('cost_180d'))} | "
                f"{format_decimal(row.get('roas_180d'), 4)} | {row.get('reason', '')} |"
            )
        lines.append("")

    md_path.write_text("\n".join(lines), encoding="utf-8")
    return json_path, md_path


def print_plan(plan: ProductCl1BestsellerSplitPlan) -> None:
    set_catch_all = sum(1 for row in plan.changes if row.get("action") == "set_catch_all")
    clear_catch_all = sum(1 for row in plan.changes if row.get("action") == "clear_catch_all")
    print(f"\nPlan: {TASK_NAME}")
    print_table(
        ["Metryka", "Wartosc"],
        [
            ["Dane glowne", f"{plan.metric_window.get('start', '')} - {plan.metric_window.get('end', '')}"],
            ["Dane swiezosci", f"{plan.recent_window.get('start', '')} - {plan.recent_window.get('end', '')}"],
            ["Segmenty CL1", str(len(plan.segment_summaries))],
            ["Zmiany CL4", str(len(plan.changes))],
            ["Do catch_all", str(set_catch_all)],
            ["Do glownej kampanii", str(clear_catch_all)],
        ],
    )

    if plan.segment_summaries:
        print("\nPodsumowanie po CL1")
        print_table(
            ["CL1", "Status", "Produkty", "Rdzen", "Glowna", "Catch_all", "Do catch_all", "Do glownej"],
            [
                [
                    row.get("custom_label_1", ""),
                    row.get("status", ""),
                    str(row.get("managed_products", 0)),
                    str(row.get("core_rank_target", 0)),
                    str(row.get("final_core_products", 0)),
                    str(row.get("final_catch_all_products", 0)),
                    str(row.get("planned_set_catch_all", 0)),
                    str(row.get("planned_clear_catch_all", 0)),
                ]
                for row in plan.segment_summaries
            ],
        )

    skipped = [row for row in plan.segment_summaries if row.get("status") == "skipped"]
    if skipped:
        print("\nPominiete segmenty")
        print_table(
            ["CL1", "Powod"],
            [[row.get("custom_label_1", ""), row.get("reason", "")] for row in skipped],
        )

    if plan.changes:
        print("\nNajwazniejsze dzialania")
        rows = [
            [
                str(index),
                row.get("custom_label_1", ""),
                str(row.get("rank", "")),
                row.get("offer_id", ""),
                row.get("current_custom_label_4", ""),
                row.get("target_custom_label_4", ""),
                format_decimal(row.get("score")),
            ]
            for index, row in enumerate(plan.changes[:10], 1)
        ]
        print_table(["Nr", "CL1", "Rank", "Produkt", "Obecne CL4", "Docelowe CL4", "Score"], rows)
        if len(plan.changes) > 10:
            print(f"... oraz {len(plan.changes) - 10} kolejnych zmian")

    if plan.warnings:
        print("\nUwagi")
        print_table(["Nr", "Uwaga"], [[str(index), warning] for index, warning in enumerate(plan.warnings, 1)])


def apply_plan(client_config: ClientConfig, plan: ProductCl1BestsellerSplitPlan, plan_path: str) -> None:
    api_url, api_key, adspro_client_id = adspro_credentials(client_config)
    applied = []
    errors = []
    for row in plan.changes:
        try:
            set_adspro_cl4(api_url, api_key, adspro_client_id, row["offer_id"], row["target_custom_label_4"])
            applied.append(row)
        except Exception as exc:
            errors.append({**row, "error": str(exc)})
            print(f"Blad zapisu {row['offer_id']}: {exc}")

    print("\nWynik wdrozenia zmian CL4")
    print(f"Wdrozono zmian: {len(applied)}")
    print(f"Bledy: {len(errors)}")

    change_rows = [
        {
            "klient": client_config.domain,
            "produkt": row["offer_id"],
            "CL1": row.get("custom_label_1", ""),
            "pole": "custom_label_4",
            "obecnie": row.get("current_custom_label_4", ""),
            "docelowo": row.get("target_custom_label_4", ""),
        }
        for row in applied
    ]
    changes_path = append_change_markdown(client_config.domain, TASK_NAME, change_rows)
    history_path = append_history(
        client_config.domain,
        {
            "task": TASK_NAME,
            "status": "wdrozono zmiany" if not errors else "wdrozono czesciowo",
            "product": ", ".join(row["offer_id"] for row in applied[:10]),
            "plan_path": plan_path,
            "summary": {
                "changes": len(plan.changes),
                "applied": len(applied),
                "errors": len(errors),
                "set_catch_all": sum(1 for row in applied if row.get("action") == "set_catch_all"),
                "clear_catch_all": sum(1 for row in applied if row.get("action") == "clear_catch_all"),
            },
        },
    )
    print(f"Historia JSONL: {history_path}")
    print(f"Historia Markdown: {changes_path}")


def print_next_navigation(domain: str) -> None:
    print("\nCo dalej:")
    print(f"1. Lista zadan klienta {domain}")
    print("2. Lista klientow")
    print("3. Zakoncz")
    print("\nKomendy:")
    print(f"1 -> python gads.py analiza-klienta --client {domain}")
    print("2 -> python gads.py analiza-klienta")


def run_split_pla_cl1_bestsellers(
    client_config: ClientConfig,
    global_rules: dict,
    plan_only: bool = False,
    apply_plan_path: str | None = None,
    confirm_apply: str | None = None,
    show_navigation: bool = True,
) -> None:
    if apply_plan_path:
        if confirm_apply != "TAK":
            print("Do wdrozenia planu wymagane jest --confirm-apply TAK.")
            if show_navigation:
                print_next_navigation(client_config.domain)
            return
        data = json.loads(Path(apply_plan_path).read_text(encoding="utf-8"))
        if data.get("client") != client_config.domain:
            print(f"Plan jest dla klienta {data.get('client')}, a wybrano {client_config.domain}.")
            if show_navigation:
                print_next_navigation(client_config.domain)
            return
        if data.get("task") != TASK_ID:
            print(f"Plan jest dla zadania {data.get('task')}, a oczekiwano {TASK_ID}.")
            if show_navigation:
                print_next_navigation(client_config.domain)
            return
        plan = ProductCl1BestsellerSplitPlan.from_dict(data)
        print_plan(plan)
        apply_plan(client_config, plan, apply_plan_path)
        if show_navigation:
            print_next_navigation(client_config.domain)
        return

    print(f"\nKlient: {client_config.domain}")
    print("Przygotowuje plan podzialu produktow CL1 na bestsellery i catch_all...")
    plan = build_product_cl1_bestseller_split_plan(client_config, global_rules)
    print_plan(plan)
    json_path, md_path = save_plan(client_config.domain, plan)
    print(f"\nPlan JSON: {json_path}")
    print(f"Plan Markdown: {md_path}")

    append_history(
        client_config.domain,
        {
            "task": TASK_NAME,
            "status": "plan przygotowany",
            "summary": {
                "segments": len(plan.segment_summaries),
                "changes": len(plan.changes),
                "set_catch_all": sum(1 for row in plan.changes if row.get("action") == "set_catch_all"),
                "clear_catch_all": sum(1 for row in plan.changes if row.get("action") == "clear_catch_all"),
            },
        },
    )

    if plan_only:
        print("\nTryb plan-only: zmiany nie zostaly wdrozone.")
    else:
        print("\nBrak automatycznego wdrozenia. Uzyj zapisanego planu i potwierdzenia, aby wdrozyc zmiany CL4.")
    if show_navigation:
        print_next_navigation(client_config.domain)
