from __future__ import annotations

import json
from dataclasses import dataclass
from pathlib import Path
from typing import Any

from google.protobuf import field_mask_pb2

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 ..knowledge.store import rules_for_task
from ..table import print_table


TASK_ID = "optimize_shopping_troas_ag"
TASK_NAME = "Automatyzacja tROAS per grupa reklam PLA"

MIN_CLICKS_ALL_TIME = 100
CONVERSIONS_TRIGGER = 10
MIN_ROAS_DELTA = 1.0
MAX_TROAS_STEP = 0.5


SCOPE = [
    {
        "area": "Zakres",
        "check": "Analizuje tylko aktywne grupy reklam w aktywnych kampaniach Standard Shopping.",
    },
    {
        "area": "Dane 30 dni",
        "check": "Liczy realny ROAS grupy reklam z kosztu i wartosci konwersji z ostatnich 30 dni.",
    },
    {
        "area": "100 klikow",
        "check": "Grupy reklam z mniej niz 100 klikami od poczatku trafiaja tylko na watchliste.",
    },
    {
        "area": "Trigger 10 konwersji",
        "check": "Podbicie tROAS wymaga co najmniej 10 nowych konwersji narastajaco od ostatniej zmiany tROAS albo od poczatku, jesli tROAS nie byl jeszcze zmieniany przez to zadanie.",
    },
    {
        "area": "Stopniowanie",
        "check": "Jedna analiza moze podniesc tROAS grupy reklam maksymalnie o 0.5.",
    },
    {
        "area": "Rollback",
        "check": "Jesli po zmianie realny ROAS z 30 dni spada ponizej ustawionego tROAS, plan proponuje przywrocenie poprzedniej wartosci.",
    },
]


OUT_OF_SCOPE = [
    "Performance Max",
    "Search",
    "kampanie Shopping bez grup reklam",
    "automatyczne wdrozenie bez akceptacji uzytkownika",
    "pauzowanie grup reklam z niskim albo zerowym ROAS",
]


@dataclass
class ShoppingTroasAgPlan:
    currency_code: str
    ad_groups: list[dict]
    watchlist: list[dict]
    target_changes: list[dict]
    rollback_changes: list[dict]
    scope: list[dict]
    out_of_scope: list[str]
    knowledge_rules: list[dict]
    warnings: list[str]

    def to_dict(self) -> dict:
        changes = self.rollback_changes + self.target_changes
        return {
            "task": TASK_ID,
            "task_name": TASK_NAME,
            "currency_code": self.currency_code,
            "ad_groups": self.ad_groups,
            "watchlist": self.watchlist,
            "target_changes": self.target_changes,
            "rollback_changes": self.rollback_changes,
            "changes": changes,
            "scope": self.scope,
            "out_of_scope": self.out_of_scope,
            "knowledge_rules": self.knowledge_rules,
            "warnings": self.warnings,
        }

    @classmethod
    def from_dict(cls, data: dict) -> "ShoppingTroasAgPlan":
        return cls(
            currency_code=data.get("currency_code", ""),
            ad_groups=data.get("ad_groups", []),
            watchlist=data.get("watchlist", []),
            target_changes=data.get("target_changes", []),
            rollback_changes=data.get("rollback_changes", []),
            scope=data.get("scope", []),
            out_of_scope=data.get("out_of_scope", []),
            knowledge_rules=data.get("knowledge_rules", []),
            warnings=data.get("warnings", []),
        )


def enum_name(value: Any) -> str:
    name = getattr(value, "name", None)
    if name:
        return name
    return str(value)


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


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


def micros_to_amount(value: int | float) -> float:
    return round(float(value or 0) / 1_000_000, 2)


def format_money(value: int | float, currency_code: str) -> str:
    suffix = f" {currency_code}" if currency_code else ""
    return f"{micros_to_amount(value):.2f}{suffix}"


def format_decimal(value: int | float) -> str:
    return f"{float(value or 0):.2f}"


def troas_label(value: int | float) -> str:
    if not value:
        return "brak override"
    return f"{float(value):.2f}"


def baseline_path(domain: str) -> Path:
    return client_dir(domain) / "troas_ag_baseline.json"


def load_baseline(domain: str) -> dict:
    path = baseline_path(domain)
    if not path.exists():
        return {"ad_groups": {}}
    try:
        data = json.loads(path.read_text(encoding="utf-8"))
    except json.JSONDecodeError:
        return {"ad_groups": {}}
    if not isinstance(data, dict):
        return {"ad_groups": {}}
    data.setdefault("ad_groups", {})
    return data


def save_baseline(domain: str, data: dict) -> Path:
    path = baseline_path(domain)
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(json.dumps(data, ensure_ascii=False, indent=2, sort_keys=True), encoding="utf-8")
    return path


def real_roas(cost_micros: int, conversion_value: float) -> float:
    cost = micros_to_amount(cost_micros)
    if cost <= 0:
        return 0.0
    return round(float(conversion_value or 0) / cost, 2)


def effective_troas(row: dict) -> float:
    ad_group_target = safe_float(row.get("ad_group_target_roas"))
    if ad_group_target > 0:
        return ad_group_target
    return safe_float(row.get("campaign_target_roas")) or safe_float(row.get("campaign_maximize_target_roas"))


def current_target_source(row: dict) -> str:
    if safe_float(row.get("ad_group_target_roas")) > 0:
        return "grupa reklam"
    if safe_float(row.get("campaign_target_roas")) > 0 or safe_float(row.get("campaign_maximize_target_roas")) > 0:
        return "kampania"
    return "brak celu"


def suggested_raise(row: dict) -> dict | None:
    if safe_int(row.get("clicks_all_time")) < MIN_CLICKS_ALL_TIME:
        return None
    conversions_delta = safe_float(row.get("new_conversions_since_baseline"))
    if conversions_delta < CONVERSIONS_TRIGGER:
        return None
    current = safe_float(row.get("troas_pre"))
    actual = safe_float(row.get("real_roas_30d"))
    if current <= 0:
        return None
    if actual - current <= MIN_ROAS_DELTA:
        return None
    target = min(round(actual - 0.5, 2), round(current + MAX_TROAS_STEP, 2))
    if target <= current:
        return None
    return {
        "change_type": "raise",
        "campaign_id": row["campaign_id"],
        "campaign_name": row["campaign_name"],
        "ad_group_id": row["ad_group_id"],
        "ad_group_name": row["ad_group_name"],
        "current_troas": current,
        "target_troas": target,
        "previous_troas": current,
        "real_roas_30d": actual,
        "delta": round(actual - current, 2),
        "new_conversions_since_baseline": conversions_delta,
        "clicks_all_time": row.get("clicks_all_time", 0),
        "reason": "realny ROAS z 30 dni jest wyzszy od tROAS o ponad 1.0 i minelo co najmniej 10 nowych konwersji od ostatniej zmiany tROAS",
    }


def suggested_rollback(row: dict) -> dict | None:
    baseline = row.get("baseline") or {}
    last_target = safe_float(baseline.get("last_target_troas"))
    previous = safe_float(baseline.get("previous_troas"))
    actual = safe_float(row.get("real_roas_30d"))
    if last_target <= 0 or previous <= 0:
        return None
    if actual >= last_target:
        return None
    return {
        "change_type": "rollback",
        "campaign_id": row["campaign_id"],
        "campaign_name": row["campaign_name"],
        "ad_group_id": row["ad_group_id"],
        "ad_group_name": row["ad_group_name"],
        "current_troas": row["troas_pre"],
        "target_troas": previous,
        "previous_troas": previous,
        "real_roas_30d": actual,
        "delta": round(actual - last_target, 2),
        "new_conversions_since_baseline": row.get("new_conversions_since_baseline", 0),
        "clicks_all_time": row.get("clicks_all_time", 0),
        "reason": "realny ROAS po zmianie jest nizszy niz ustawiony tROAS; plan proponuje przywrocenie poprzedniej wartosci",
    }


def watchlist_reason(row: dict) -> str:
    clicks = safe_int(row.get("clicks_all_time"))
    actual = safe_float(row.get("real_roas_30d"))
    conversions = safe_float(row.get("conversions_30d"))
    if clicks < MIN_CLICKS_ALL_TIME and (actual <= 0 or conversions <= 0):
        return "mniej niz 100 klikow od poczatku i niski albo zerowy ROAS - obserwuj, bez akcji"
    if clicks < MIN_CLICKS_ALL_TIME:
        return "mniej niz 100 klikow od poczatku - obserwuj, bez akcji"
    return ""


def fetch_currency_code(google_client, customer_id: str) -> str:
    rows = run_query(
        google_client,
        customer_id,
        """
        SELECT
          customer.currency_code
        FROM customer
        """,
    )
    if not rows:
        return ""
    return str(rows[0].customer.currency_code or "")


def fetch_all_time_clicks(google_client, customer_id: str) -> dict[str, int]:
    rows = run_query(
        google_client,
        customer_id,
        """
        SELECT
          ad_group.id,
          metrics.clicks
        FROM ad_group
        WHERE campaign.status = 'ENABLED'
          AND ad_group.status = 'ENABLED'
          AND campaign.advertising_channel_type = 'SHOPPING'
        """,
    )
    clicks: dict[str, int] = {}
    for row in rows:
        clicks[str(row.ad_group.id)] = safe_int(row.metrics.clicks)
    return clicks


def fetch_all_time_conversions(google_client, customer_id: str) -> dict[str, float]:
    rows = run_query(
        google_client,
        customer_id,
        """
        SELECT
          ad_group.id,
          metrics.conversions
        FROM ad_group
        WHERE campaign.status = 'ENABLED'
          AND ad_group.status = 'ENABLED'
          AND campaign.advertising_channel_type = 'SHOPPING'
        """,
    )
    conversions: dict[str, float] = {}
    for row in rows:
        conversions[str(row.ad_group.id)] = round(safe_float(row.metrics.conversions), 2)
    return conversions


def fetch_shopping_ad_groups(client_config: ClientConfig) -> tuple[str, list[dict], list[str]]:
    warnings: list[str] = []
    google_client = get_google_ads_client(use_proto_plus=True)
    customer_id = client_config.safe_customer_id
    currency_code = fetch_currency_code(google_client, customer_id)
    all_time_clicks = fetch_all_time_clicks(google_client, customer_id)
    all_time_conversions = fetch_all_time_conversions(google_client, customer_id)
    rows = run_query(
        google_client,
        customer_id,
        """
        SELECT
          campaign.id,
          campaign.name,
          campaign.status,
          campaign.advertising_channel_type,
          campaign.target_roas.target_roas,
          campaign.maximize_conversion_value.target_roas,
          ad_group.id,
          ad_group.name,
          ad_group.status,
          ad_group.type,
          ad_group.target_roas,
          ad_group.effective_target_roas,
          ad_group.effective_target_roas_source,
          metrics.cost_micros,
          metrics.clicks,
          metrics.conversions,
          metrics.conversions_value
        FROM ad_group
        WHERE campaign.status = 'ENABLED'
          AND ad_group.status = 'ENABLED'
          AND campaign.advertising_channel_type = 'SHOPPING'
          AND segments.date DURING LAST_30_DAYS
        """,
    )

    records = []
    for row in rows:
        ad_group = row.ad_group
        campaign = row.campaign
        ad_group_id = str(ad_group.id)
        cost_micros = safe_int(row.metrics.cost_micros)
        conversion_value = safe_float(row.metrics.conversions_value)
        record = {
            "campaign_id": str(campaign.id),
            "campaign_name": campaign.name,
            "campaign_status": enum_name(campaign.status),
            "channel_type": enum_name(campaign.advertising_channel_type),
            "campaign_target_roas": safe_float(campaign.target_roas.target_roas),
            "campaign_maximize_target_roas": safe_float(campaign.maximize_conversion_value.target_roas),
            "ad_group_id": ad_group_id,
            "ad_group_name": ad_group.name,
            "ad_group_status": enum_name(ad_group.status),
            "ad_group_type": enum_name(ad_group.type),
            "ad_group_target_roas": safe_float(ad_group.target_roas),
            "ad_group_effective_target_roas": safe_float(ad_group.effective_target_roas),
            "ad_group_effective_target_roas_source": enum_name(ad_group.effective_target_roas_source),
            "cost_30d_micros": cost_micros,
            "clicks_30d": safe_int(row.metrics.clicks),
            "clicks_all_time": all_time_clicks.get(ad_group_id, 0),
            "conversions_30d": round(safe_float(row.metrics.conversions), 2),
            "conversions_all_time": all_time_conversions.get(ad_group_id, 0.0),
            "conversion_value_30d": round(conversion_value, 2),
            "real_roas_30d": real_roas(cost_micros, conversion_value),
        }
        record["troas_pre"] = effective_troas(record)
        record["troas_source"] = current_target_source(record)
        records.append(record)
    if not records:
        warnings.append("Nie znaleziono aktywnych grup reklam w aktywnych kampaniach Standard Shopping z danymi z ostatnich 30 dni.")
    return currency_code, records, warnings


def build_shopping_troas_ag_plan(client_config: ClientConfig) -> ShoppingTroasAgPlan:
    warnings: list[str] = []
    try:
        currency_code, ad_groups, fetch_warnings = fetch_shopping_ad_groups(client_config)
        warnings.extend(fetch_warnings)
    except Exception as exc:
        currency_code = ""
        ad_groups = []
        warnings.append(f"Nie udalo sie pobrac grup reklam Shopping z Google Ads API: {exc}")

    baseline = load_baseline(client_config.domain)
    baseline_rows = baseline.get("ad_groups", {})
    watchlist: list[dict] = []
    target_changes: list[dict] = []
    rollback_changes: list[dict] = []

    for row in ad_groups:
        stored = baseline_rows.get(row["ad_group_id"], {})
        row["baseline"] = stored
        if "baseline_conversions_all_time" in stored:
            baseline_conversions = safe_float(stored.get("baseline_conversions_all_time"))
        elif stored:
            baseline_conversions = safe_float(row.get("conversions_all_time"))
            warnings.append(
                f"Baseline tROAS dla grupy {row['ad_group_name']} ma stary format bez konwersji all-time; "
                "licznik nowych konwersji zostal zresetowany od tej analizy."
            )
        else:
            baseline_conversions = 0.0
        row["baseline_conversions_all_time"] = baseline_conversions
        row["baseline_known"] = bool(stored)
        row["new_conversions_since_baseline"] = round(max(0.0, row["conversions_all_time"] - baseline_conversions), 2)
        row["watchlist_reason"] = watchlist_reason(row)
        if row["watchlist_reason"]:
            watchlist.append(row)
            continue
        rollback = suggested_rollback(row)
        if rollback:
            rollback_changes.append(rollback)
            continue
        change = suggested_raise(row)
        if change:
            target_changes.append(change)

    ad_groups.sort(key=lambda item: (item["campaign_name"], item["ad_group_name"]))
    watchlist.sort(
        key=lambda item: (
            -safe_int(item["clicks_all_time"]),
            -safe_float(item["conversions_30d"]),
            item["campaign_name"],
            item["ad_group_name"],
        )
    )
    target_changes.sort(key=lambda item: (-item["delta"], item["campaign_name"], item["ad_group_name"]))
    rollback_changes.sort(key=lambda item: (item["campaign_name"], item["ad_group_name"]))

    rules = rules_for_task(TASK_ID)
    knowledge_rules = [
        {
            "id": rule.id,
            "topic": rule.topic,
            "rule_type": rule.rule_type,
            "condition": rule.condition,
            "recommendation": rule.recommendation,
            "risk": rule.risk,
            "source": rule.source,
        }
        for rule in rules
    ]

    if not knowledge_rules:
        warnings.append("Do tego zadania nie przypisano jeszcze regul z bazy wiedzy.")

    return ShoppingTroasAgPlan(
        currency_code=currency_code,
        ad_groups=ad_groups,
        watchlist=watchlist,
        target_changes=target_changes,
        rollback_changes=rollback_changes,
        scope=SCOPE,
        out_of_scope=OUT_OF_SCOPE,
        knowledge_rules=knowledge_rules,
        warnings=warnings,
    )


def save_shopping_troas_ag_plan(domain: str, plan: ShoppingTroasAgPlan) -> 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 = [
        "# Plan: Automatyzacja tROAS per grupa reklam PLA",
        "",
        f"Klient: {domain}",
        f"Utworzono: {ts.isoformat(timespec='seconds')}",
        "",
        "## Podsumowanie",
        "",
        f"- Grupy reklam Shopping z danymi 30 dni: {len(plan.ad_groups)}",
        f"- Kandydaci do podniesienia tROAS: {len(plan.target_changes)}",
        f"- Kandydaci do rollbacku: {len(plan.rollback_changes)}",
        f"- Watchlista bez akcji: {len(plan.watchlist)}",
        f"- Reguly wiedzy przypisane do zadania: {len(plan.knowledge_rules)}",
        "",
    ]
    if plan.warnings:
        lines.extend(["## Uwagi", ""])
        lines.extend(f"- {item}" for item in plan.warnings)
        lines.append("")
    lines.extend(["## Zakres zadania", "", "| Obszar | Co sprawdzic |", "| --- | --- |"])
    for row in plan.scope:
        lines.append(f"| {row.get('area', '')} | {row.get('check', '')} |")
    lines.append("")
    if plan.target_changes:
        lines.extend(
            [
                "## Kandydaci do podniesienia tROAS",
                "",
                "| Kampania | Grupa reklam | tROAS pre | Real ROAS | Delta | Nowy tROAS | Konwersje od zmiany | Klikniecia all-time |",
                "| --- | --- | --- | --- | --- | --- | --- | --- |",
            ]
        )
        for change in plan.target_changes:
            lines.append(
                f"| {change['campaign_name']} | {change['ad_group_name']} | "
                f"{format_decimal(change['current_troas'])} | {format_decimal(change['real_roas_30d'])} | "
                f"{format_decimal(change['delta'])} | {format_decimal(change['target_troas'])} | "
                f"{format_decimal(change['new_conversions_since_baseline'])} | {change['clicks_all_time']} |"
            )
        lines.append("")
    if plan.rollback_changes:
        lines.extend(
            [
                "## Kandydaci do rollbacku",
                "",
                "| Kampania | Grupa reklam | Obecny tROAS | Real ROAS | Przywroc tROAS | Powod |",
                "| --- | --- | --- | --- | --- | --- |",
            ]
        )
        for change in plan.rollback_changes:
            lines.append(
                f"| {change['campaign_name']} | {change['ad_group_name']} | "
                f"{format_decimal(change['current_troas'])} | {format_decimal(change['real_roas_30d'])} | "
                f"{format_decimal(change['target_troas'])} | {change['reason']} |"
            )
        lines.append("")
    if plan.watchlist:
        lines.extend(
            [
                "## Watchlista bez akcji",
                "",
                "| Kampania | Grupa reklam | Klikniecia all-time | Konwersje 30 dni | Konwersje all-time | Konwersje od zmiany | Real ROAS | Powod |",
                "| --- | --- | --- | --- | --- | --- | --- | --- |",
            ]
        )
        for row in plan.watchlist:
            lines.append(
                f"| {row['campaign_name']} | {row['ad_group_name']} | {row['clicks_all_time']} | "
                f"{format_decimal(row['conversions_30d'])} | {format_decimal(row.get('conversions_all_time', 0))} | "
                f"{format_decimal(row.get('new_conversions_since_baseline', 0))} | {format_decimal(row['real_roas_30d'])} | "
                f"{row['watchlist_reason']} |"
            )
        lines.append("")
    if plan.knowledge_rules:
        lines.extend(
            [
                "## Reguly z bazy wiedzy",
                "",
                "| ID | Temat | Rekomendacja | Ryzyko |",
                "| --- | --- | --- | --- |",
            ]
        )
        for rule in plan.knowledge_rules:
            lines.append(
                f"| {rule.get('id', '')} | {rule.get('topic', '')} | "
                f"{rule.get('recommendation', '')} | {rule.get('risk', '')} |"
            )
        lines.append("")
    lines.extend(["## Poza zakresem tego zadania", ""])
    lines.extend(f"- {item}" for item in plan.out_of_scope)
    lines.append("")
    md_path.write_text("\n".join(lines), encoding="utf-8")
    return json_path, md_path


def print_shopping_troas_ag_plan(plan: ShoppingTroasAgPlan) -> None:
    print("\nPlan automatyzacji tROAS per grupa reklam PLA")
    print_table(
        ["Metryka", "Liczba"],
        [
            ["Grupy reklam Shopping z danymi 30 dni", str(len(plan.ad_groups))],
            ["Kandydaci do podniesienia tROAS", str(len(plan.target_changes))],
            ["Kandydaci do rollbacku", str(len(plan.rollback_changes))],
            ["Watchlista bez akcji", str(len(plan.watchlist))],
            ["Reguly wiedzy", str(len(plan.knowledge_rules))],
        ],
    )
    if plan.warnings:
        print("\nUwagi")
        print_table(["Nr", "Uwaga"], [[str(index), item] for index, item in enumerate(plan.warnings, 1)])
    print("\nZakres zadania")
    print_table(
        ["Nr", "Obszar", "Co sprawdzic"],
        [[str(index), row["area"], row["check"]] for index, row in enumerate(plan.scope, 1)],
    )
    if plan.target_changes:
        print("\nKandydaci do podniesienia tROAS")
        print_table(
            ["Nr", "Kampania", "Grupa reklam", "tROAS pre", "Real ROAS", "Delta", "Nowy tROAS", "Konw. od zmiany"],
            [
                [
                    str(index),
                    change["campaign_name"],
                    change["ad_group_name"],
                    format_decimal(change["current_troas"]),
                    format_decimal(change["real_roas_30d"]),
                    format_decimal(change["delta"]),
                    format_decimal(change["target_troas"]),
                    format_decimal(change["new_conversions_since_baseline"]),
                ]
                for index, change in enumerate(plan.target_changes, 1)
            ],
        )
    if plan.rollback_changes:
        print("\nKandydaci do rollbacku")
        print_table(
            ["Nr", "Kampania", "Grupa reklam", "Obecny tROAS", "Real ROAS", "Przywroc", "Powod"],
            [
                [
                    str(index),
                    change["campaign_name"],
                    change["ad_group_name"],
                    format_decimal(change["current_troas"]),
                    format_decimal(change["real_roas_30d"]),
                    format_decimal(change["target_troas"]),
                    change["reason"],
                ]
                for index, change in enumerate(plan.rollback_changes, 1)
            ],
        )
    if plan.watchlist:
        print("\nWatchlista bez akcji")
        print_table(
            ["Nr", "Kampania", "Grupa reklam", "Klikniecia", "Konw. 30d", "Konw. all-time", "Konw. od zmiany", "ROAS", "Powod"],
            [
                [
                    str(index),
                    row["campaign_name"],
                    row["ad_group_name"],
                    str(row["clicks_all_time"]),
                    format_decimal(row["conversions_30d"]),
                    format_decimal(row.get("conversions_all_time", 0)),
                    format_decimal(row.get("new_conversions_since_baseline", 0)),
                    format_decimal(row["real_roas_30d"]),
                    row["watchlist_reason"],
                ]
                for index, row in enumerate(plan.watchlist, 1)
            ],
        )
    if plan.knowledge_rules:
        print("\nReguly z bazy wiedzy")
        print_table(
            ["Nr", "ID", "Temat", "Rekomendacja"],
            [
                [str(index), rule["id"], rule["topic"], rule["recommendation"]]
                for index, rule in enumerate(plan.knowledge_rules[:10], 1)
            ],
        )


def apply_troas_changes(client_config: ClientConfig, plan: ShoppingTroasAgPlan, show_navigation: bool = True) -> None:
    changes = plan.rollback_changes + plan.target_changes
    changed = 0
    errors: list[str] = []
    if changes:
        google_client = get_google_ads_client(use_proto_plus=True)
        customer_id = client_config.safe_customer_id
        service = google_client.get_service("AdGroupService")
        operations = []
        for change in changes:
            op = google_client.get_type("AdGroupOperation")
            ad_group = op.update
            ad_group.resource_name = service.ad_group_path(customer_id, change["ad_group_id"])
            ad_group.target_roas = float(change["target_troas"])
            op.update_mask = field_mask_pb2.FieldMask(paths=["target_roas"])
            operations.append(op)
        try:
            response = service.mutate_ad_groups(customer_id=customer_id, operations=operations)
            changed = len(response.results)
        except Exception as exc:
            errors.append(str(exc))

    print("\nWynik wdrozenia zmian tROAS grup reklam")
    print(f"Zmieniono grup reklam: {changed}")
    print(f"Bledy: {len(errors)}")
    for error in errors:
        print(f"Blad: {error}")

    if changed and not errors:
        baseline = load_baseline(client_config.domain)
        baseline_rows = baseline.setdefault("ad_groups", {})
        ts = now_local().isoformat(timespec="seconds")
        ad_groups_by_id = {row["ad_group_id"]: row for row in plan.ad_groups}
        for change in changes:
            source = ad_groups_by_id.get(change["ad_group_id"], {})
            baseline_rows[change["ad_group_id"]] = {
                "updated_at": ts,
                "campaign_id": change["campaign_id"],
                "campaign_name": change["campaign_name"],
                "ad_group_id": change["ad_group_id"],
                "ad_group_name": change["ad_group_name"],
                "previous_troas": change["current_troas"],
                "last_target_troas": change["target_troas"],
                "baseline_conversions_all_time": source.get("conversions_all_time", 0),
                "baseline_conversions_30d": source.get("conversions_30d", 0),
                "baseline_real_roas_30d": source.get("real_roas_30d", 0),
                "change_type": change["change_type"],
            }
        baseline_file = save_baseline(client_config.domain, baseline)
        print(f"Baseline tROAS AG: {baseline_file}")

    rows = [
        {
            "klient": client_config.domain,
            "kampania": change.get("campaign_name", ""),
            "grupa reklam": change.get("ad_group_name", ""),
            "czynnosc": "Rollback tROAS" if change.get("change_type") == "rollback" else "Podniesienie tROAS",
            "produkt": f"{format_decimal(change.get('current_troas', 0))} -> {format_decimal(change.get('target_troas', 0))}",
        }
        for change in changes
    ]
    changes_path = append_change_markdown(client_config.domain, TASK_NAME, rows)
    history_path = append_history(
        client_config.domain,
        {
            "task": TASK_NAME,
            "status": "wdrozono zmiany tROAS AG" if changes and not errors else "audyt oznaczony jako wykonany",
            "campaign": ", ".join(change.get("campaign_name", "") for change in changes[:10]),
            "summary": {
                "ad_groups": len(plan.ad_groups),
                "target_changes": len(plan.target_changes),
                "rollback_changes": len(plan.rollback_changes),
                "changed": changed,
                "errors": len(errors),
            },
        },
    )
    print(f"Historia JSONL: {history_path}")
    print(f"Historia Markdown: {changes_path}")
    if show_navigation:
        print_next_navigation(client_config.domain)


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_optimize_shopping_troas_ag(
    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:
    _ = global_rules
    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
        plan_data = json.loads(Path(apply_plan_path).read_text(encoding="utf-8"))
        if plan_data.get("client") != client_config.domain:
            print(f"Plan jest dla klienta {plan_data.get('client')}, a wybrano {client_config.domain}.")
            if show_navigation:
                print_next_navigation(client_config.domain)
            return
        plan = ShoppingTroasAgPlan.from_dict(plan_data)
        print_shopping_troas_ag_plan(plan)
        apply_troas_changes(client_config, plan, show_navigation=show_navigation)
        return

    print(f"\nKlient: {client_config.domain}")
    print("Przygotowuje plan automatyzacji tROAS per grupa reklam PLA...")
    plan = build_shopping_troas_ag_plan(client_config)
    print_shopping_troas_ag_plan(plan)
    json_path, md_path = save_shopping_troas_ag_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",
            "campaign": ", ".join(change["campaign_name"] for change in (plan.rollback_changes + plan.target_changes)[:10]),
            "summary": {
                "ad_groups": len(plan.ad_groups),
                "target_changes": len(plan.target_changes),
                "rollback_changes": len(plan.rollback_changes),
                "watchlist": len(plan.watchlist),
                "knowledge_rules": len(plan.knowledge_rules),
            },
        },
    )

    if plan_only:
        print("\nTryb plan-only: zmiany nie zostaly wdrozone.")
        if show_navigation:
            print_next_navigation(client_config.domain)
        return

    print("\nBrak automatycznego wdrozenia. Uzyj zapisanego planu i potwierdzenia, aby wdrozyc zmiany tROAS.")
    if show_navigation:
        print_next_navigation(client_config.domain)
