from __future__ import annotations

import json
from dataclasses import dataclass
from datetime import datetime
from pathlib import Path

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 = "check_budget_usage"
TASK_NAME = "Sprawdzenie wykorzystania budzetu"


SCOPE = [
    {
        "area": "Wydatki 7 dni",
        "check": "Porownaj koszt z ostatnich 7 dni z oczekiwanym wydatkiem wynikajacym z budzetu dziennego.",
    },
    {
        "area": "Pacing",
        "check": "Oznacz kampanie, ktore wydaja bardzo malo albo prawie caly tygodniowy limit budzetu.",
    },
    {
        "area": "Utrata wyswietlania przez budzet",
        "check": "Polacz wykorzystanie budzetu z utrata udzialu w wyswietleniach przez budzet i rentownoscia kampanii.",
    },
    {
        "area": "Brak wydatkow",
        "check": "Wskaz aktywne kampanie z budzetem, ktore nie wydaly srodkow w ostatnich 7 dniach.",
    },
    {
        "area": "Budzet wspoldzielony",
        "check": "Pokaz nazwe budzetu, zeby latwiej wychwycic kampanie korzystajace z tego samego budzetu.",
    },
]


OUT_OF_SCOPE = [
    "zmiany stawek i strategii ustalania stawek",
    "ocena Docelowego ROAS albo Docelowego CPA",
    "analiza zapytan, wykluczen i jakosci ruchu",
    "wdrazanie zmian budzetowych na koncie",
]


@dataclass
class BudgetUsagePlan:
    currency_code: str
    campaigns: list[dict]
    scope: list[dict]
    out_of_scope: list[str]
    knowledge_rules: list[dict]
    warnings: list[str]
    budget_changes: list[dict] | None = None

    def to_dict(self) -> dict:
        return {
            "task": TASK_ID,
            "task_name": TASK_NAME,
            "currency_code": self.currency_code,
            "campaigns": self.campaigns,
            "scope": self.scope,
            "out_of_scope": self.out_of_scope,
            "knowledge_rules": self.knowledge_rules,
            "warnings": self.warnings,
            "budget_changes": self.budget_changes or [],
            "changes": self.budget_changes or [],
        }

    @classmethod
    def from_dict(cls, data: dict) -> "BudgetUsagePlan":
        return cls(
            currency_code=data.get("currency_code", ""),
            campaigns=data.get("campaigns", []),
            scope=data.get("scope", []),
            out_of_scope=data.get("out_of_scope", []),
            knowledge_rules=data.get("knowledge_rules", []),
            warnings=data.get("warnings", []),
            budget_changes=data.get("budget_changes", data.get("changes", [])),
        )


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


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


def percent(value: int | float, total: int | float) -> float:
    if not total:
        return 0.0
    return round((float(value) / float(total)) * 100, 1)


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 percent_label(value: int | float | None) -> str:
    if value is None:
        return "-"
    return f"{float(value) * 100:.2f}%"


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


def days_since_label(value) -> str:
    if value is None:
        return "brak danych"
    if value == 0:
        return "dzis"
    if value == 1:
        return "1 dzien temu"
    return f"{value} dni temu"


def budget_recommendations(campaigns: list[dict]) -> list[dict]:
    return [
        campaign
        for campaign in campaigns
        if campaign.get("budget_recommendation", {}).get("level") not in {"ok", None}
    ]


def pacing_label(cost_7d_micros: int, expected_7d_micros: int) -> str:
    if expected_7d_micros <= 0:
        return "brak budzetu"
    usage = percent(cost_7d_micros, expected_7d_micros)
    if cost_7d_micros <= 0:
        return "brak wydatkow"
    if usage < 30:
        return "niskie wykorzystanie"
    if usage > 95:
        return "blisko limitu"
    return "w normie"


def last_budget_change_date(domain: str, campaign_id: str, campaign_name: str) -> datetime | None:
    """Najnowsza data wdrozonej zmiany budzetu dla kampanii, z plikow historii."""
    base = client_dir(domain) / "history"
    if not base.exists():
        return None
    latest: datetime | None = None
    for path in sorted(base.glob("*.jsonl")):
        try:
            content = path.read_text(encoding="utf-8-sig")
        except OSError:
            continue
        for raw in content.splitlines():
            raw = raw.strip()
            if not raw:
                continue
            try:
                event = json.loads(raw)
            except json.JSONDecodeError:
                continue
            if event.get("status") != "wdrozono zmiany budzetu":
                continue
            changes = event.get("budget_changes") or []
            if changes:
                matched = any(str(item.get("campaign_id")) == str(campaign_id) for item in changes)
            else:
                # starsze wpisy bez szczegolow - dopasowanie po nazwie kampanii
                matched = (
                    event.get("task") == TASK_NAME
                    and bool(campaign_name)
                    and campaign_name in (event.get("campaign") or "")
                )
            if not matched:
                continue
            try:
                ts = datetime.fromisoformat(event["timestamp"])
            except (KeyError, ValueError):
                continue
            if latest is None or ts > latest:
                latest = ts
    return latest


def build_budget_recommendation(campaign: dict, min_days_between_budget_changes: int = 0) -> dict:
    usage = float(campaign.get("usage_percent") or 0)
    lost_budget = float(campaign.get("search_budget_lost_impression_share") or 0)
    cost_micros = int(campaign.get("cost_7d_micros") or 0)
    daily_budget_micros = int(campaign.get("daily_budget_micros") or 0)
    conversion_value = float(campaign.get("conversions_value") or 0)
    roas = float(campaign.get("roas") or 0)
    days_since = campaign.get("days_since_budget_change")

    increase_percent = 0
    if lost_budget >= 0.5:
        increase_percent = 30
    elif lost_budget >= 0.3:
        increase_percent = 25
    elif lost_budget >= 0.15:
        increase_percent = 15

    recommended_budget_micros = daily_budget_micros
    if increase_percent:
        recommended_budget_micros = int(round(daily_budget_micros * (1 + increase_percent / 100)))
    budget_delta_micros = recommended_budget_micros - daily_budget_micros

    if usage >= 90 and lost_budget >= 0.3 and conversion_value > 0 and roas >= 2:
        recommendation = {
            "level": "wysokie",
            "action": "rozważ podniesienie budżetu",
            "reason": "kampania prawie wykorzystuje budzet, traci duzo wyswietlen przez budzet i ma dodatnia rentownosc",
            "suggested_budget_change_percent": increase_percent,
            "current_daily_budget_micros": daily_budget_micros,
            "recommended_daily_budget_micros": recommended_budget_micros,
            "budget_delta_micros": budget_delta_micros,
        }
    elif usage >= 90 and lost_budget >= 0.15 and conversion_value > 0 and roas >= 1:
        recommendation = {
            "level": "srednie",
            "action": "sprawdz mozliwosc podniesienia budżetu",
            "reason": "kampania wykorzystuje budzet i traci czesc wyswietlen przez budzet",
            "suggested_budget_change_percent": increase_percent,
            "current_daily_budget_micros": daily_budget_micros,
            "recommended_daily_budget_micros": recommended_budget_micros,
            "budget_delta_micros": budget_delta_micros,
        }
    elif usage >= 90 and lost_budget >= 0.15:
        recommendation = {
            "level": "ostroznie",
            "action": "nie podnoś budżetu bez oceny rentowności",
            "reason": "widac utrate przez budzet, ale brakuje wystarczajacej wartosci konwersji",
            "suggested_budget_change_percent": 0,
            "current_daily_budget_micros": daily_budget_micros,
            "recommended_daily_budget_micros": daily_budget_micros,
            "budget_delta_micros": 0,
        }
    elif cost_micros <= 0:
        recommendation = {
            "level": "do sprawdzenia",
            "action": "sprawdz brak wydatkow",
            "reason": "aktywna kampania nie wydala srodkow w ostatnich 7 dniach",
            "suggested_budget_change_percent": 0,
            "current_daily_budget_micros": daily_budget_micros,
            "recommended_daily_budget_micros": daily_budget_micros,
            "budget_delta_micros": 0,
        }
    else:
        recommendation = {
            "level": "ok",
            "action": "bez zmiany budzetu",
            "reason": "brak jednoczesnego sygnalu wysokiego wykorzystania i utraty przez budzet",
            "suggested_budget_change_percent": 0,
            "current_daily_budget_micros": daily_budget_micros,
            "recommended_daily_budget_micros": daily_budget_micros,
            "budget_delta_micros": 0,
        }

    recommendation["days_since_budget_change"] = days_since
    recommendation["min_days_between_budget_changes"] = min_days_between_budget_changes

    # Wstrzymaj rekomendacje podniesienia budzetu, jesli budzet zmieniono zbyt niedawno.
    if (
        recommendation["level"] in {"wysokie", "srednie"}
        and min_days_between_budget_changes > 0
        and days_since is not None
        and days_since < min_days_between_budget_changes
    ):
        recommendation = {
            "level": "wstrzymane",
            "action": "nie zmieniaj budzetu jeszcze",
            "reason": (
                f"budzet zmieniony {days_since} dni temu, minimum {min_days_between_budget_changes}; "
                f"pierwotna rekomendacja: {recommendation['level']}"
            ),
            "suggested_budget_change_percent": 0,
            "current_daily_budget_micros": daily_budget_micros,
            "recommended_daily_budget_micros": daily_budget_micros,
            "budget_delta_micros": 0,
            "days_since_budget_change": days_since,
            "min_days_between_budget_changes": min_days_between_budget_changes,
        }

    return recommendation


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_budget_campaigns(
    client_config: ClientConfig,
    min_days_between_budget_changes: int = 0,
) -> tuple[str, list[dict]]:
    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)
    rows = run_query(
        google_client,
        customer_id,
        """
        SELECT
          campaign.id,
          campaign.name,
          campaign.status,
          campaign.advertising_channel_type,
          campaign_budget.id,
          campaign_budget.name,
          campaign_budget.amount_micros,
          campaign_budget.delivery_method,
          metrics.cost_micros,
          metrics.conversions,
          metrics.conversions_value,
          metrics.search_impression_share,
          metrics.search_budget_lost_impression_share,
          metrics.search_rank_lost_impression_share
        FROM campaign
        WHERE campaign.status = 'ENABLED'
          AND segments.date DURING LAST_7_DAYS
        """,
    )

    campaigns = []
    for row in rows:
        campaign = row.campaign
        budget = row.campaign_budget
        daily_budget_micros = int(budget.amount_micros or 0)
        metrics = row.metrics
        cost_7d_micros = int(metrics.cost_micros or 0)
        expected_7d_micros = daily_budget_micros * 7
        conversions_value = float(metrics.conversions_value or 0)
        cost = micros_to_amount(cost_7d_micros)
        record = {
            "campaign_id": str(campaign.id),
            "campaign_name": campaign.name,
            "status": enum_name(campaign.status),
            "channel_type": enum_name(campaign.advertising_channel_type),
            "budget_id": str(budget.id),
            "budget_name": budget.name,
            "budget_delivery_method": enum_name(budget.delivery_method),
            "daily_budget_micros": daily_budget_micros,
            "expected_7d_micros": expected_7d_micros,
            "cost_7d_micros": cost_7d_micros,
            "avg_daily_cost_micros": int(cost_7d_micros / 7),
            "usage_percent": percent(cost_7d_micros, expected_7d_micros),
            "pacing_label": pacing_label(cost_7d_micros, expected_7d_micros),
            "conversions": round(float(metrics.conversions or 0), 2),
            "conversions_value": round(conversions_value, 2),
            "roas": round(conversions_value / cost, 2) if cost else 0,
            "search_impression_share": float(metrics.search_impression_share or 0),
            "search_budget_lost_impression_share": float(metrics.search_budget_lost_impression_share or 0),
            "search_rank_lost_impression_share": float(metrics.search_rank_lost_impression_share or 0),
        }
        last_change = last_budget_change_date(
            client_config.domain, record["campaign_id"], record["campaign_name"]
        )
        record["days_since_budget_change"] = (
            (now_local() - last_change).days if last_change else None
        )
        record["budget_recommendation"] = build_budget_recommendation(
            record, min_days_between_budget_changes
        )
        campaigns.append(record)
    return currency_code, campaigns


def build_budget_usage_plan(
    client_config: ClientConfig,
    global_rules: dict | None = None,
) -> BudgetUsagePlan:
    warnings = []
    budget_rules = client_config.effective_rules(global_rules or {}, "budget_usage")
    min_days_between_budget_changes = int(
        budget_rules.get("min_days_between_budget_changes", 0) or 0
    )
    try:
        currency_code, campaigns = fetch_budget_campaigns(
            client_config, min_days_between_budget_changes
        )
    except Exception as exc:
        currency_code = ""
        campaigns = []
        warnings.append(f"Nie udalo sie pobrac budzetow z Google Ads API: {exc}")

    if not campaigns:
        warnings.append("Nie znaleziono aktywnych kampanii z danymi kosztow z ostatnich 7 dni albo nie udalo sie ich pobrac.")

    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. "
            "Reguly budzetowe bedziemy dopisywac osobno po akceptacji uzytkownika."
        )

    campaigns.sort(key=lambda row: (-row["usage_percent"], row["campaign_name"]))
    return BudgetUsagePlan(
        currency_code=currency_code,
        campaigns=campaigns,
        scope=SCOPE,
        out_of_scope=OUT_OF_SCOPE,
        knowledge_rules=knowledge_rules,
        warnings=warnings,
        budget_changes=[],
    )


def save_budget_usage_plan(domain: str, plan: BudgetUsagePlan) -> 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: Sprawdzenie wykorzystania budzetu",
        "",
        f"Klient: {domain}",
        f"Utworzono: {ts.isoformat(timespec='seconds')}",
        "",
        "## Podsumowanie",
        "",
        f"- Kampanie aktywne z danymi 7 dni: {len(plan.campaigns)}",
        f"- Reguly wiedzy przypisane do zadania: {len(plan.knowledge_rules)}",
        f"- Rekomendacje budzetowe do decyzji: {len(budget_recommendations(plan.campaigns))}",
        f"- Zmiany budzetu do wdrozenia: {len(plan.budget_changes or [])}",
        "",
    ]
    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("")
    lines.extend(["## Poza zakresem tego zadania", ""])
    lines.extend(f"- {item}" for item in plan.out_of_scope)
    lines.append("")
    if plan.campaigns:
        lines.extend(
            [
                "## Wykorzystanie budzetu z ostatnich 7 dni",
                "",
                "| Kampania | Typ | Budzet dzienny | Koszt 7 dni | Uzycie 7 dni | Utrata przez budzet | ROAS | Status | Ost. zmiana budzetu | Budzet |",
                "| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |",
            ]
        )
        for campaign in plan.campaigns:
            lines.append(
                f"| {campaign['campaign_name']} | {campaign['channel_type']} | "
                f"{format_money(campaign['daily_budget_micros'], plan.currency_code)} | "
                f"{format_money(campaign['cost_7d_micros'], plan.currency_code)} | "
                f"{campaign['usage_percent']:.1f}% | "
                f"{percent_label(campaign.get('search_budget_lost_impression_share'))} | "
                f"{roas_label(campaign.get('roas', 0))} | "
                f"{campaign['pacing_label']} | "
                f"{days_since_label(campaign.get('days_since_budget_change'))} | "
                f"{campaign['budget_name']} |"
            )
        lines.append("")
    recommendations = budget_recommendations(plan.campaigns)
    if recommendations:
        lines.extend(
            [
                "## Rekomendacje budzetowe do decyzji",
                "",
                "| Kampania | Waznosc | Obecnie | Propozycja | Zmiana | Powod |",
                "| --- | --- | --- | --- | --- | --- |",
            ]
        )
        for campaign in recommendations:
            recommendation = campaign["budget_recommendation"]
            lines.append(
                f"| {campaign['campaign_name']} | {recommendation['level']} | "
                f"{format_money(recommendation['current_daily_budget_micros'], plan.currency_code)} | "
                f"{format_money(recommendation['recommended_daily_budget_micros'], plan.currency_code)} | "
                f"+{recommendation['suggested_budget_change_percent']}% "
                f"({format_money(recommendation['budget_delta_micros'], plan.currency_code)}) | "
                f"{recommendation['reason']} |"
            )
        lines.append("")
    if plan.budget_changes:
        lines.extend(
            [
                "## Zmiany budzetu do wdrozenia",
                "",
                "| Kampania | Budzet | Obecnie | Docelowo | Zmiana | Powod |",
                "| --- | --- | --- | --- | --- | --- |",
            ]
        )
        for change in plan.budget_changes:
            lines.append(
                f"| {change.get('campaign_name', '')} | {change.get('budget_name', '')} | "
                f"{format_money(change.get('current_daily_budget_micros', 0), plan.currency_code)} | "
                f"{format_money(change.get('target_daily_budget_micros', 0), plan.currency_code)} | "
                f"{format_money(change.get('delta_micros', 0), plan.currency_code)} | "
                f"{change.get('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("")
    md_path.write_text("\n".join(lines), encoding="utf-8")
    return json_path, md_path


def print_budget_usage_plan(plan: BudgetUsagePlan) -> None:
    print("\nPlan sprawdzenia wykorzystania budzetu")
    print_table(
        ["Metryka", "Liczba"],
        [
            ["Kampanie z danymi 7 dni", str(len(plan.campaigns))],
            ["Reguly wiedzy", str(len(plan.knowledge_rules))],
            ["Rekomendacje budzetowe", str(len(budget_recommendations(plan.campaigns)))],
            ["Zmiany budzetu do wdrozenia", str(len(plan.budget_changes or []))],
        ],
    )
    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)],
    )
    print("\nPoza zakresem")
    print_table(["Nr", "Nie analizujemy tutaj"], [[str(index), item] for index, item in enumerate(plan.out_of_scope, 1)])
    if plan.campaigns:
        print("\nWykorzystanie budzetu z ostatnich 7 dni")
        print_table(
            ["Nr", "Kampania", "Typ", "Budzet dzienny", "Koszt 7 dni", "Uzycie", "Utrata budz.", "ROAS", "Status", "Ost. zm. budz."],
            [
                [
                    str(index),
                    campaign["campaign_name"],
                    campaign["channel_type"],
                    format_money(campaign["daily_budget_micros"], plan.currency_code),
                    format_money(campaign["cost_7d_micros"], plan.currency_code),
                    f"{campaign['usage_percent']:.1f}%",
                    percent_label(campaign.get("search_budget_lost_impression_share")),
                    roas_label(campaign.get("roas", 0)),
                    campaign["pacing_label"],
                    days_since_label(campaign.get("days_since_budget_change")),
                ]
                for index, campaign in enumerate(plan.campaigns, 1)
            ],
        )
    if plan.budget_changes:
        print("\nZmiany budzetu do wdrozenia")
        print_table(
            ["Nr", "Kampania", "Budzet", "Obecnie", "Docelowo", "Zmiana", "Powod"],
            [
                [
                    str(index),
                    change.get("campaign_name", ""),
                    change.get("budget_name", ""),
                    format_money(change.get("current_daily_budget_micros", 0), plan.currency_code),
                    format_money(change.get("target_daily_budget_micros", 0), plan.currency_code),
                    format_money(change.get("delta_micros", 0), plan.currency_code),
                    change.get("reason", ""),
                ]
                for index, change in enumerate(plan.budget_changes, 1)
            ],
        )
    recommendations = budget_recommendations(plan.campaigns)
    if recommendations:
        print("\nRekomendacje budzetowe do decyzji")
        print_table(
            ["Nr", "Kampania", "Waznosc", "Obecnie", "Propozycja", "Zmiana", "Powod"],
            [
                [
                    str(index),
                    campaign["campaign_name"],
                    campaign["budget_recommendation"]["level"],
                    format_money(campaign["budget_recommendation"]["current_daily_budget_micros"], plan.currency_code),
                    format_money(
                        campaign["budget_recommendation"]["recommended_daily_budget_micros"],
                        plan.currency_code,
                    ),
                    (
                        f"+{campaign['budget_recommendation']['suggested_budget_change_percent']}% "
                        f"({format_money(campaign['budget_recommendation']['budget_delta_micros'], plan.currency_code)})"
                    ),
                    campaign["budget_recommendation"]["reason"],
                ]
                for index, campaign in enumerate(recommendations, 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)
            ],
        )
        if len(plan.knowledge_rules) > 10:
            print(f"... oraz {len(plan.knowledge_rules) - 10} kolejnych regul")


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 apply_budget_usage_plan(
    client_config: ClientConfig,
    plan: BudgetUsagePlan,
    show_navigation: bool = True,
) -> None:
    budget_changes = plan.budget_changes or []
    changed = 0
    errors = []
    if budget_changes:
        google_client = get_google_ads_client(use_proto_plus=True)
        customer_id = client_config.safe_customer_id
        service = google_client.get_service("CampaignBudgetService")
        operations = []
        for change in budget_changes:
            op = google_client.get_type("CampaignBudgetOperation")
            budget = op.update
            budget.resource_name = service.campaign_budget_path(customer_id, change["budget_id"])
            budget.amount_micros = int(change["target_daily_budget_micros"])
            op.update_mask = field_mask_pb2.FieldMask(paths=["amount_micros"])
            operations.append(op)

        if operations:
            try:
                response = service.mutate_campaign_budgets(customer_id=customer_id, operations=operations)
                changed = len(response.results)
            except Exception as exc:
                errors.append(str(exc))

    if budget_changes:
        print("\nWynik wdrozenia zmian budzetu")
        print(f"Zmieniono budzetow: {changed}")
        print(f"Bledy: {len(errors)}")
        for error in errors:
            print(f"Blad: {error}")
    else:
        print("\nTo zadanie jest audytem budzetow i nie ma zmian budzetu do wdrozenia.")

    rows = [
        {
            "klient": client_config.domain,
            "kampania": change.get("campaign_name", ""),
            "czynnosc": "Zmien budzet dzienny",
            "grupa reklam": "",
            "produkt": (
                f"{format_money(change.get('current_daily_budget_micros', 0), plan.currency_code)} -> "
                f"{format_money(change.get('target_daily_budget_micros', 0), plan.currency_code)}"
            ),
        }
        for change in budget_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 budzetu" if budget_changes and not errors else "audyt oznaczony jako wykonany",
            "campaign": ", ".join(
                change.get("campaign_name", "") for change in budget_changes
            )
            or ", ".join(campaign["campaign_name"] for campaign in plan.campaigns[:10]),
            "budget_changes": [
                {
                    "campaign_id": change.get("campaign_id", ""),
                    "campaign_name": change.get("campaign_name", ""),
                    "budget_id": change.get("budget_id", ""),
                    "target_daily_budget_micros": change.get("target_daily_budget_micros", 0),
                }
                for change in budget_changes
            ],
            "summary": {
                "campaigns": len(plan.campaigns),
                "knowledge_rules": len(plan.knowledge_rules),
                "budget_changes": len(budget_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 run_check_budget_usage(
    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 oznaczenia audytu jako wykonanego 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 = BudgetUsagePlan.from_dict(plan_data)
        print_budget_usage_plan(plan)
        apply_budget_usage_plan(client_config, plan, show_navigation=show_navigation)
        return

    print(f"\nKlient: {client_config.domain}")
    print("Przygotowuje plan sprawdzenia wykorzystania budzetu...")
    plan = build_budget_usage_plan(client_config, global_rules)
    print_budget_usage_plan(plan)
    json_path, md_path = save_budget_usage_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(campaign["campaign_name"] for campaign in plan.campaigns[:10]),
            "summary": {
                "campaigns": len(plan.campaigns),
                "knowledge_rules": len(plan.knowledge_rules),
                "changes": 0,
            },
        },
    )

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

    print("\nBrak zmian do wdrozenia. To zadanie tworzy plan audytu budzetow.")
    if show_navigation:
        print_next_navigation(client_config.domain)
