from __future__ import annotations

import json
from collections import Counter
from dataclasses import dataclass
from pathlib import Path
from typing import Any, Callable

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 = "ai_account_audit"
TASK_NAME = "Audyt AI calego konta"
DATE_RANGE = "LAST_30_DAYS"


DEFAULT_LIMITS = {
    "ad_groups": 500,
    "ads": 500,
    "responsive_search_ads": 200,
    "asset_groups": 500,
    "keywords": 500,
    "search_terms": 500,
    "shopping_products": 500,
    "shopping_product_statuses": 500,
    "devices": 500,
}


AUDIT_BRIEF = [
    {
        "area": "Strategia konta",
        "question": "Czy struktura kampanii wspiera aktualny cel biznesowy, marze, sezonowosc i priorytety kategorii?",
    },
    {
        "area": "Kampanie",
        "question": "Ktore kampanie warto rozwijac, ograniczac, laczyc, rozdzielic albo zamknac?",
    },
    {
        "area": "Braki w strukturze",
        "question": "Jakich kampanii, grup reklam, odbiorcow, produktow albo segmentow ruchu brakuje?",
    },
    {
        "area": "Budzet i stawki",
        "question": "Gdzie budzet jest marnowany, gdzie brakuje skali i gdzie cele stawek moga blokowac wynik?",
    },
    {
        "area": "Search",
        "question": "Czy slowa kluczowe, dopasowania, zapytania i reklamy odpowiadaja intencji uzytkownika?",
    },
    {
        "area": "Shopping i produkty",
        "question": "Ktore produkty, marki i kategorie powinny byc mocniej promowane, wyciszone albo poprawione w feedzie?",
    },
    {
        "area": "Pomiar",
        "question": "Czy konwersje i wartosc konwersji sa wystarczajaco wiarygodne do decyzji optymalizacyjnych?",
    },
    {
        "area": "Plan dalszych dzialan",
        "question": "Jakie decyzje zapisujemy na najblizsze 7, 30 i 90 dni oraz jak bedziemy mierzyc efekt?",
    },
]


CONVERSATION_QUESTIONS = [
    "Jaki jest najwazniejszy cel konta na najblizsze 30-90 dni: przychod, ROAS, liczba leadow, marza, udzial w rynku?",
    "Czy sa kategorie, marki albo produkty, ktore maja priorytet biznesowy mimo slabszych danych z Google Ads?",
    "Czy sa kampanie albo segmenty, ktorych nie wolno ograniczac z powodow strategicznych?",
    "Jakie zmiany byly robione ostatnio poza Google Ads: ceny, dostepnosc, promocje, strona, feed, tracking?",
    "Jaki poziom ryzyka akceptujemy: ostrozny audyt, umiarkowane porzadki czy agresywne skalowanie?",
]


FUTURE_PLAN_TEMPLATE = {
    "status": "do_uzupelnienia_przez_agenta_ai",
    "strategic_goal": "",
    "main_diagnosis": "",
    "what_works_well": [],
    "what_to_fix": [],
    "campaigns_to_add": [],
    "campaigns_to_pause_or_remove": [],
    "campaigns_to_scale": [],
    "campaigns_to_limit": [],
    "feed_and_product_actions": [],
    "search_and_keyword_actions": [],
    "measurement_actions": [],
    "questions_for_user": [],
    "next_7_days": [],
    "next_30_days": [],
    "next_90_days": [],
    "success_metrics": [],
    "risks": [],
}


@dataclass
class AiAccountAuditPlan:
    account: dict
    date_range: str
    limits: dict
    datasets: dict[str, list[dict]]
    summaries: dict[str, Any]
    audit_brief: list[dict]
    conversation_questions: list[str]
    future_plan: dict
    knowledge_rules: list[dict]
    warnings: list[str]

    def to_dict(self) -> dict:
        return {
            "task": TASK_ID,
            "task_name": TASK_NAME,
            "account": self.account,
            "date_range": self.date_range,
            "limits": self.limits,
            "datasets": self.datasets,
            "summaries": self.summaries,
            "audit_brief": self.audit_brief,
            "conversation_questions": self.conversation_questions,
            "future_plan": self.future_plan,
            "knowledge_rules": self.knowledge_rules,
            "warnings": self.warnings,
            "changes": [],
        }

    @classmethod
    def from_dict(cls, data: dict) -> "AiAccountAuditPlan":
        return cls(
            account=data.get("account", {}),
            date_range=data.get("date_range", DATE_RANGE),
            limits=data.get("limits", {}),
            datasets=data.get("datasets", {}),
            summaries=data.get("summaries", {}),
            audit_brief=data.get("audit_brief", []),
            conversation_questions=data.get("conversation_questions", []),
            future_plan=data.get("future_plan", {}),
            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 or "")


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


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


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


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


def cpa(cost_micros: int, conversions: float) -> float:
    if not conversions:
        return 0.0
    return round(micros_to_amount(cost_micros) / float(conversions), 2)


def rate(numerator: int | float, denominator: int | float) -> float:
    if not denominator:
        return 0.0
    return round((float(numerator) / float(denominator)) * 100, 2)


def md_cell(value: Any) -> str:
    return str(value or "").replace("|", "\\|").replace("\n", " ").strip()


def format_money_micros(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_money_amount(value: int | float, currency_code: str) -> str:
    suffix = f" {currency_code}" if currency_code else ""
    return f"{float(value or 0):.2f}{suffix}"


def metric_dict(metrics: Any) -> dict:
    cost_micros = safe_int(getattr(metrics, "cost_micros", 0))
    clicks = safe_int(getattr(metrics, "clicks", 0))
    impressions = safe_int(getattr(metrics, "impressions", 0))
    conversions = safe_float(getattr(metrics, "conversions", 0))
    conversion_value = safe_float(getattr(metrics, "conversions_value", 0))
    return {
        "impressions": impressions,
        "clicks": clicks,
        "cost_micros": cost_micros,
        "conversions": round(conversions, 2),
        "conversion_value": round(conversion_value, 2),
        "ctr_percent": rate(clicks, impressions),
        "avg_cpc_micros": int(cost_micros / clicks) if clicks else 0,
        "conversion_rate_percent": rate(conversions, clicks),
        "roas": roas(conversion_value, cost_micros),
        "cpa": cpa(cost_micros, conversions),
    }


def get_attr(obj: Any, path: str, default: Any = "") -> Any:
    current = obj
    for part in path.split("."):
        try:
            current = getattr(current, part)
        except Exception:
            return default
        if current is None:
            return default
    return current


def text_asset_values(assets: Any) -> list[str]:
    values = []
    for asset in assets or []:
        text = str(getattr(asset, "text", "") or "").strip()
        if text:
            values.append(text)
    return values


def issue_to_dict(issue: Any) -> dict:
    return {
        "code": enum_name(getattr(issue, "code", "")),
        "severity": enum_name(getattr(issue, "severity", "")),
        "resolution": enum_name(getattr(issue, "resolution", "")),
        "attribute": str(getattr(issue, "attribute", "") or ""),
        "description": str(getattr(issue, "description", "") or ""),
        "detail": str(getattr(issue, "detail", "") or ""),
    }


def parse_limits(global_rules: dict, client_config: ClientConfig) -> dict:
    rules = client_config.effective_rules(global_rules, "ai_account_audit")
    limits = dict(DEFAULT_LIMITS)
    for key in list(limits):
        value = rules.get(key)
        if value is None:
            value = rules.get(f"{key}_limit")
        if value is None:
            continue
        try:
            limits[key] = max(1, int(value))
        except (TypeError, ValueError):
            continue
    return limits


def fetch_account(google_client, customer_id: str, warnings: list[str]) -> dict:
    try:
        rows = run_query(
            google_client,
            customer_id,
            """
            SELECT
              customer.id,
              customer.descriptive_name,
              customer.currency_code,
              customer.time_zone
            FROM customer
            LIMIT 1
            """,
        )
        if rows:
            customer = rows[0].customer
            return {
                "customer_id": str(customer.id),
                "descriptive_name": str(customer.descriptive_name or ""),
                "currency_code": str(customer.currency_code or ""),
                "time_zone": str(customer.time_zone or ""),
            }
    except Exception as exc:
        warnings.append(f"Nie udalo sie pobrac podstawowych danych konta: {compact_error(exc)}")
    return {
        "customer_id": customer_id,
        "descriptive_name": "",
        "currency_code": "",
        "time_zone": "",
    }


def fetch_dataset(
    google_client,
    customer_id: str,
    label: str,
    query: str,
    builder: Callable[[Any], dict],
    warnings: list[str],
) -> list[dict]:
    try:
        rows = run_query(google_client, customer_id, query)
    except Exception as exc:
        warnings.append(f"Nie udalo sie pobrac danych: {label}: {compact_error(exc)}")
        return []
    result = []
    for row in rows:
        try:
            result.append(builder(row))
        except Exception as exc:
            warnings.append(f"Pominieto wiersz danych {label}: {compact_error(exc)}")
    return result


def compact_error(exc: Exception) -> str:
    message = str(exc).strip()
    if not message:
        return exc.__class__.__name__
    if "PROHIBITED" in message:
        return "Google Ads API odrzucilo kombinacje pol w zapytaniu."
    if "UNRECOGNIZED_FIELD" in message:
        return "Google Ads API nie rozpoznalo jednego z pol zapytania."
    if "PERMISSION_DENIED" in message:
        return "brak uprawnien do pobrania tych danych."
    return message.splitlines()[0][:500]


def row_campaign(row: Any) -> dict:
    campaign = row.campaign
    budget = row.campaign_budget
    return {
        "campaign_id": str(campaign.id),
        "campaign_name": str(campaign.name or ""),
        "status": enum_name(campaign.status),
        "channel_type": enum_name(campaign.advertising_channel_type),
        "bidding_strategy_type": enum_name(campaign.bidding_strategy_type),
        "budget_id": str(budget.id or ""),
        "budget_name": str(budget.name or ""),
        "daily_budget_micros": safe_int(budget.amount_micros),
        **metric_dict(row.metrics),
    }


def row_ad_group(row: Any) -> dict:
    return {
        "campaign_id": str(row.campaign.id),
        "campaign_name": str(row.campaign.name or ""),
        "channel_type": enum_name(row.campaign.advertising_channel_type),
        "ad_group_id": str(row.ad_group.id),
        "ad_group_name": str(row.ad_group.name or ""),
        "ad_group_status": enum_name(row.ad_group.status),
        "ad_group_type": enum_name(row.ad_group.type_),
        **metric_dict(row.metrics),
    }


def row_ad_status(row: Any) -> dict:
    ad = row.ad_group_ad.ad
    return {
        "campaign_id": str(row.campaign.id),
        "campaign_name": str(row.campaign.name or ""),
        "channel_type": enum_name(row.campaign.advertising_channel_type),
        "ad_group_id": str(row.ad_group.id),
        "ad_group_name": str(row.ad_group.name or ""),
        "ad_id": str(ad.id),
        "ad_name": str(ad.name or ""),
        "ad_type": enum_name(ad.type_),
        "ad_status": enum_name(row.ad_group_ad.status),
        "approval_status": enum_name(row.ad_group_ad.policy_summary.approval_status),
    }


def row_rsa(row: Any) -> dict:
    ad = row.ad_group_ad.ad
    rsa = ad.responsive_search_ad
    headlines = text_asset_values(rsa.headlines)
    descriptions = text_asset_values(rsa.descriptions)
    return {
        "campaign_id": str(row.campaign.id),
        "campaign_name": str(row.campaign.name or ""),
        "ad_group_id": str(row.ad_group.id),
        "ad_group_name": str(row.ad_group.name or ""),
        "ad_id": str(ad.id),
        "ad_status": enum_name(row.ad_group_ad.status),
        "final_urls": [str(url) for url in ad.final_urls],
        "headlines": headlines,
        "descriptions": descriptions,
        "headline_count": len(headlines),
        "description_count": len(descriptions),
    }


def row_asset_group(row: Any) -> dict:
    return {
        "campaign_id": str(row.campaign.id),
        "campaign_name": str(row.campaign.name or ""),
        "asset_group_id": str(row.asset_group.id),
        "asset_group_name": str(row.asset_group.name or ""),
        "asset_group_status": enum_name(row.asset_group.status),
    }


def row_keyword(row: Any) -> dict:
    criterion = row.ad_group_criterion
    quality = criterion.quality_info
    return {
        "campaign_id": str(row.campaign.id),
        "campaign_name": str(row.campaign.name or ""),
        "ad_group_id": str(row.ad_group.id),
        "ad_group_name": str(row.ad_group.name or ""),
        "keyword": str(criterion.keyword.text or ""),
        "match_type": enum_name(criterion.keyword.match_type),
        "criterion_status": enum_name(criterion.status),
        "quality_score": safe_int(quality.quality_score),
        "creative_quality": enum_name(quality.creative_quality_score),
        "landing_page_quality": enum_name(quality.post_click_quality_score),
        "expected_ctr": enum_name(quality.search_predicted_ctr),
        **metric_dict(row.metrics),
    }


def row_search_term(row: Any) -> dict:
    return {
        "campaign_id": str(row.campaign.id),
        "campaign_name": str(row.campaign.name or ""),
        "channel_type": enum_name(row.campaign.advertising_channel_type),
        "ad_group_id": str(row.ad_group.id),
        "ad_group_name": str(row.ad_group.name or ""),
        "search_term": str(row.search_term_view.search_term or ""),
        **metric_dict(row.metrics),
    }


def row_shopping_product(row: Any) -> dict:
    return {
        "campaign_name": str(row.campaign.name or ""),
        "ad_group_name": str(row.ad_group.name or ""),
        "item_id": str(row.segments.product_item_id or ""),
        "product_title": str(row.segments.product_title or ""),
        "brand": str(row.segments.product_brand or ""),
        "category_l1": str(row.segments.product_category_level1 or ""),
        **metric_dict(row.metrics),
    }


def row_shopping_product_status(row: Any) -> dict:
    product = row.shopping_product
    issues = [issue_to_dict(issue) for issue in getattr(product, "issues", [])]
    return {
        "merchant_center_id": str(product.merchant_center_id or ""),
        "channel": enum_name(product.channel),
        "language_code": str(product.language_code or ""),
        "feed_label": str(product.feed_label or ""),
        "item_id": str(product.item_id or ""),
        "title": str(product.title or ""),
        "status": enum_name(product.status),
        "issue_count": len(issues),
        "issues": issues,
    }


def row_conversion_action(row: Any) -> dict:
    action = row.conversion_action
    return {
        "conversion_action_id": str(action.id),
        "resource_name": str(action.resource_name or ""),
        "name": str(action.name or ""),
        "status": enum_name(action.status),
        "type": enum_name(action.type_),
        "category": enum_name(action.category),
        "primary_for_goal": bool(get_attr(action, "primary_for_goal", False)),
        "include_in_conversions_metric": bool(get_attr(action, "include_in_conversions_metric", False)),
        "counting_type": enum_name(get_attr(action, "counting_type", "")),
        "default_value": safe_float(get_attr(action, "value_settings.default_value", 0)),
        "default_currency_code": str(get_attr(action, "value_settings.default_currency_code", "") or ""),
        "always_use_default_value": bool(get_attr(action, "value_settings.always_use_default_value", False)),
        "click_window_days": safe_int(get_attr(action, "click_through_lookback_window_days", 0)),
        "view_window_days": safe_int(get_attr(action, "view_through_lookback_window_days", 0)),
        "attribution_model": enum_name(get_attr(action, "attribution_model_settings.attribution_model", "")),
    }


def row_conversion_performance(row: Any) -> dict:
    return {
        "conversion_action": str(row.segments.conversion_action or ""),
        "conversion_action_name": str(row.segments.conversion_action_name or ""),
        "conversion_action_category": enum_name(row.segments.conversion_action_category),
        "conversions": round(safe_float(row.metrics.conversions), 2),
        "conversion_value": round(safe_float(row.metrics.conversions_value), 2),
        "all_conversions": round(safe_float(row.metrics.all_conversions), 2),
        "all_conversions_value": round(safe_float(row.metrics.all_conversions_value), 2),
    }


def row_device(row: Any) -> dict:
    return {
        "campaign_id": str(row.campaign.id),
        "campaign_name": str(row.campaign.name or ""),
        "channel_type": enum_name(row.campaign.advertising_channel_type),
        "device": enum_name(row.segments.device),
        **metric_dict(row.metrics),
    }


def build_datasets(google_client, customer_id: str, limits: dict, warnings: list[str]) -> dict[str, list[dict]]:
    datasets: dict[str, list[dict]] = {}
    datasets["campaigns"] = fetch_dataset(
        google_client,
        customer_id,
        "kampanie",
        """
        SELECT
          campaign.id,
          campaign.name,
          campaign.status,
          campaign.advertising_channel_type,
          campaign.bidding_strategy_type,
          campaign_budget.id,
          campaign_budget.name,
          campaign_budget.amount_micros,
          metrics.impressions,
          metrics.clicks,
          metrics.cost_micros,
          metrics.conversions,
          metrics.conversions_value
        FROM campaign
        WHERE campaign.status != 'REMOVED'
          AND segments.date DURING LAST_30_DAYS
        """,
        row_campaign,
        warnings,
    )
    datasets["ad_groups"] = fetch_dataset(
        google_client,
        customer_id,
        "grupy reklam",
        f"""
        SELECT
          campaign.id,
          campaign.name,
          campaign.advertising_channel_type,
          ad_group.id,
          ad_group.name,
          ad_group.status,
          ad_group.type,
          metrics.impressions,
          metrics.clicks,
          metrics.cost_micros,
          metrics.conversions,
          metrics.conversions_value
        FROM ad_group
        WHERE campaign.status != 'REMOVED'
          AND ad_group.status != 'REMOVED'
          AND segments.date DURING LAST_30_DAYS
        LIMIT {limits["ad_groups"]}
        """,
        row_ad_group,
        warnings,
    )
    datasets["ads"] = fetch_dataset(
        google_client,
        customer_id,
        "reklamy",
        f"""
        SELECT
          campaign.id,
          campaign.name,
          campaign.advertising_channel_type,
          ad_group.id,
          ad_group.name,
          ad_group_ad.ad.id,
          ad_group_ad.ad.name,
          ad_group_ad.ad.type,
          ad_group_ad.status,
          ad_group_ad.policy_summary.approval_status
        FROM ad_group_ad
        WHERE campaign.status != 'REMOVED'
          AND ad_group.status != 'REMOVED'
          AND ad_group_ad.status != 'REMOVED'
        LIMIT {limits["ads"]}
        """,
        row_ad_status,
        warnings,
    )
    datasets["responsive_search_ads"] = fetch_dataset(
        google_client,
        customer_id,
        "reklamy RSA",
        f"""
        SELECT
          campaign.id,
          campaign.name,
          ad_group.id,
          ad_group.name,
          ad_group_ad.ad.id,
          ad_group_ad.status,
          ad_group_ad.ad.final_urls,
          ad_group_ad.ad.responsive_search_ad.headlines,
          ad_group_ad.ad.responsive_search_ad.descriptions
        FROM ad_group_ad
        WHERE campaign.status = 'ENABLED'
          AND ad_group.status = 'ENABLED'
          AND ad_group_ad.status != 'REMOVED'
          AND ad_group_ad.ad.type = 'RESPONSIVE_SEARCH_AD'
        LIMIT {limits["responsive_search_ads"]}
        """,
        row_rsa,
        warnings,
    )
    datasets["asset_groups"] = fetch_dataset(
        google_client,
        customer_id,
        "asset groups PMax",
        f"""
        SELECT
          campaign.id,
          campaign.name,
          asset_group.id,
          asset_group.name,
          asset_group.status
        FROM asset_group
        WHERE campaign.status != 'REMOVED'
          AND asset_group.status != 'REMOVED'
        LIMIT {limits["asset_groups"]}
        """,
        row_asset_group,
        warnings,
    )
    datasets["keywords"] = fetch_dataset(
        google_client,
        customer_id,
        "slowa kluczowe",
        f"""
        SELECT
          campaign.id,
          campaign.name,
          ad_group.id,
          ad_group.name,
          ad_group_criterion.status,
          ad_group_criterion.keyword.text,
          ad_group_criterion.keyword.match_type,
          ad_group_criterion.quality_info.quality_score,
          ad_group_criterion.quality_info.creative_quality_score,
          ad_group_criterion.quality_info.post_click_quality_score,
          ad_group_criterion.quality_info.search_predicted_ctr,
          metrics.impressions,
          metrics.clicks,
          metrics.cost_micros,
          metrics.conversions,
          metrics.conversions_value
        FROM keyword_view
        WHERE campaign.status != 'REMOVED'
          AND ad_group.status != 'REMOVED'
          AND ad_group_criterion.status != 'REMOVED'
          AND segments.date DURING LAST_30_DAYS
        ORDER BY metrics.cost_micros DESC
        LIMIT {limits["keywords"]}
        """,
        row_keyword,
        warnings,
    )
    datasets["search_terms"] = fetch_dataset(
        google_client,
        customer_id,
        "zapytania uzytkownikow",
        f"""
        SELECT
          campaign.id,
          campaign.name,
          campaign.advertising_channel_type,
          ad_group.id,
          ad_group.name,
          search_term_view.search_term,
          metrics.impressions,
          metrics.clicks,
          metrics.cost_micros,
          metrics.conversions,
          metrics.conversions_value
        FROM search_term_view
        WHERE campaign.status != 'REMOVED'
          AND ad_group.status != 'REMOVED'
          AND segments.date DURING LAST_30_DAYS
        ORDER BY metrics.cost_micros DESC
        LIMIT {limits["search_terms"]}
        """,
        row_search_term,
        warnings,
    )
    datasets["shopping_products"] = fetch_dataset(
        google_client,
        customer_id,
        "produkty Shopping",
        f"""
        SELECT
          campaign.name,
          ad_group.name,
          segments.product_title,
          segments.product_item_id,
          segments.product_brand,
          segments.product_category_level1,
          metrics.impressions,
          metrics.clicks,
          metrics.cost_micros,
          metrics.conversions,
          metrics.conversions_value
        FROM shopping_performance_view
        WHERE segments.date DURING LAST_30_DAYS
        ORDER BY metrics.cost_micros DESC
        LIMIT {limits["shopping_products"]}
        """,
        row_shopping_product,
        warnings,
    )
    datasets["shopping_product_statuses"] = fetch_dataset(
        google_client,
        customer_id,
        "statusy produktow Shopping",
        f"""
        SELECT
          shopping_product.merchant_center_id,
          shopping_product.channel,
          shopping_product.language_code,
          shopping_product.feed_label,
          shopping_product.item_id,
          shopping_product.title,
          shopping_product.status,
          shopping_product.issues
        FROM shopping_product
        LIMIT {limits["shopping_product_statuses"]}
        """,
        row_shopping_product_status,
        warnings,
    )
    datasets["conversion_actions"] = fetch_dataset(
        google_client,
        customer_id,
        "akcje konwersji",
        """
        SELECT
          conversion_action.id,
          conversion_action.resource_name,
          conversion_action.name,
          conversion_action.status,
          conversion_action.type,
          conversion_action.category,
          conversion_action.primary_for_goal,
          conversion_action.include_in_conversions_metric,
          conversion_action.counting_type,
          conversion_action.value_settings.default_value,
          conversion_action.value_settings.default_currency_code,
          conversion_action.value_settings.always_use_default_value,
          conversion_action.click_through_lookback_window_days,
          conversion_action.view_through_lookback_window_days,
          conversion_action.attribution_model_settings.attribution_model
        FROM conversion_action
        WHERE conversion_action.status != 'REMOVED'
        """,
        row_conversion_action,
        warnings,
    )
    datasets["conversion_performance"] = fetch_dataset(
        google_client,
        customer_id,
        "wyniki akcji konwersji",
        """
        SELECT
          segments.conversion_action,
          segments.conversion_action_name,
          segments.conversion_action_category,
          metrics.conversions,
          metrics.conversions_value,
          metrics.all_conversions,
          metrics.all_conversions_value
        FROM customer
        WHERE segments.date DURING LAST_30_DAYS
        """,
        row_conversion_performance,
        warnings,
    )
    datasets["devices"] = fetch_dataset(
        google_client,
        customer_id,
        "urzadzenia",
        f"""
        SELECT
          campaign.id,
          campaign.name,
          campaign.advertising_channel_type,
          segments.device,
          metrics.impressions,
          metrics.clicks,
          metrics.cost_micros,
          metrics.conversions,
          metrics.conversions_value
        FROM campaign
        WHERE campaign.status != 'REMOVED'
          AND segments.date DURING LAST_30_DAYS
        LIMIT {limits["devices"]}
        """,
        row_device,
        warnings,
    )
    sort_dataset_rows(datasets)
    return datasets


def sort_dataset_rows(datasets: dict[str, list[dict]]) -> None:
    for key in (
        "campaigns",
        "ad_groups",
        "keywords",
        "search_terms",
        "shopping_products",
        "devices",
    ):
        datasets.get(key, []).sort(key=lambda row: (-safe_int(row.get("cost_micros")), str(row)))
    datasets.get("ads", []).sort(key=lambda row: (row.get("campaign_name", ""), row.get("ad_group_name", ""), row.get("ad_type", "")))
    datasets.get("responsive_search_ads", []).sort(key=lambda row: (row.get("campaign_name", ""), row.get("ad_group_name", "")))
    datasets.get("asset_groups", []).sort(key=lambda row: (row.get("campaign_name", ""), row.get("asset_group_name", "")))
    datasets.get("shopping_product_statuses", []).sort(
        key=lambda row: (-safe_int(row.get("issue_count")), row.get("status", ""), row.get("title", ""))
    )
    datasets.get("conversion_actions", []).sort(key=lambda row: (row.get("status", ""), row.get("category", ""), row.get("name", "")))


def aggregate_metrics(rows: list[dict]) -> dict:
    total = {
        "impressions": 0,
        "clicks": 0,
        "cost_micros": 0,
        "conversions": 0.0,
        "conversion_value": 0.0,
    }
    for row in rows:
        total["impressions"] += safe_int(row.get("impressions"))
        total["clicks"] += safe_int(row.get("clicks"))
        total["cost_micros"] += safe_int(row.get("cost_micros"))
        total["conversions"] += safe_float(row.get("conversions"))
        total["conversion_value"] += safe_float(row.get("conversion_value"))
    total["conversions"] = round(total["conversions"], 2)
    total["conversion_value"] = round(total["conversion_value"], 2)
    total["ctr_percent"] = rate(total["clicks"], total["impressions"])
    total["roas"] = roas(total["conversion_value"], total["cost_micros"])
    total["cpa"] = cpa(total["cost_micros"], total["conversions"])
    return total


def counter_summary(rows: list[dict], field: str, label: str) -> list[dict]:
    counts = Counter(str(row.get(field, "") or "(brak)") for row in rows)
    return [{label: key, "count": value} for key, value in counts.most_common()]


def build_summaries(datasets: dict[str, list[dict]]) -> dict[str, Any]:
    campaigns = datasets.get("campaigns", [])
    ad_groups = datasets.get("ad_groups", [])
    ads = datasets.get("ads", [])
    keywords = datasets.get("keywords", [])
    shopping_statuses = datasets.get("shopping_product_statuses", [])
    conversion_actions = datasets.get("conversion_actions", [])
    return {
        "dataset_counts": {key: len(value) for key, value in datasets.items()},
        "account_30d": aggregate_metrics(campaigns),
        "campaigns_by_channel": counter_summary(campaigns, "channel_type", "channel_type"),
        "campaigns_by_status": counter_summary(campaigns, "status", "status"),
        "ad_groups_by_status": counter_summary(ad_groups, "ad_group_status", "status"),
        "ads_by_type": counter_summary(ads, "ad_type", "ad_type"),
        "ads_by_approval_status": counter_summary(ads, "approval_status", "approval_status"),
        "keywords_by_match_type": counter_summary(keywords, "match_type", "match_type"),
        "shopping_product_statuses": counter_summary(shopping_statuses, "status", "status"),
        "conversion_actions_by_category": counter_summary(conversion_actions, "category", "category"),
        "top_campaigns_by_cost": campaigns[:20],
        "top_ad_groups_by_cost": ad_groups[:20],
        "top_search_terms_by_cost": datasets.get("search_terms", [])[:50],
        "top_shopping_products_by_cost": datasets.get("shopping_products", [])[:50],
    }


def build_ai_account_audit_plan(client_config: ClientConfig, global_rules: dict) -> AiAccountAuditPlan:
    warnings: list[str] = []
    limits = parse_limits(global_rules, client_config)
    try:
        google_client = get_google_ads_client(use_proto_plus=True)
        customer_id = client_config.safe_customer_id
        account = fetch_account(google_client, customer_id, warnings)
        datasets = build_datasets(google_client, customer_id, limits, warnings)
    except Exception as exc:
        account = {
            "customer_id": client_config.safe_customer_id,
            "descriptive_name": "",
            "currency_code": "",
            "time_zone": "",
        }
        datasets = {}
        warnings.append(f"Nie udalo sie przygotowac pakietu audytu AI: {compact_error(exc)}")

    if not datasets.get("campaigns"):
        warnings.append("Nie znaleziono danych kampanii z ostatnich 30 dni albo API ich nie zwrocilo.")

    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_for_task(TASK_ID)
    ]
    if not knowledge_rules:
        warnings.append(
            "Do audytu AI nie przypisano jeszcze regul z bazy wiedzy. Agent powinien uzyc danych, rozmowy i istniejacych zadan szczegolowych."
        )

    return AiAccountAuditPlan(
        account=account,
        date_range=DATE_RANGE,
        limits=limits,
        datasets=datasets,
        summaries=build_summaries(datasets),
        audit_brief=AUDIT_BRIEF,
        conversation_questions=CONVERSATION_QUESTIONS,
        future_plan=dict(FUTURE_PLAN_TEMPLATE),
        knowledge_rules=knowledge_rules,
        warnings=warnings,
    )


def save_ai_account_audit_plan(domain: str, plan: AiAccountAuditPlan) -> 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")

    currency_code = plan.account.get("currency_code", "")
    account_summary = plan.summaries.get("account_30d", {})
    counts = plan.summaries.get("dataset_counts", {})
    lines = [
        "# Plan: Audyt AI calego konta",
        "",
        f"Klient: {domain}",
        f"Utworzono: {ts.isoformat(timespec='seconds')}",
        f"Zakres danych: {plan.date_range}",
        "",
        "## Cel zadania",
        "",
        "To zadanie zbiera surowy pakiet danych dla agenta AI. Skrypt nie podejmuje decyzji strategicznych i nie wdraza zmian.",
        "Agent powinien przeczytac JSON, przeprowadzic rozmowe audytowa z uzytkownikiem i dopiero po ustaleniach zapisac plan dalszych dzialan.",
        "",
        "## Pakiet danych",
        "",
        "| Zbior danych | Wiersze |",
        "| --- | ---: |",
    ]
    for name, count in counts.items():
        lines.append(f"| {md_cell(name)} | {count} |")
    lines.extend(
        [
            "",
            "## Podsumowanie konta 30 dni",
            "",
            "| Metryka | Wartosc |",
            "| --- | ---: |",
            f"| Koszt | {format_money_micros(account_summary.get('cost_micros', 0), currency_code)} |",
            f"| Klikniecia | {safe_int(account_summary.get('clicks'))} |",
            f"| Wyswietlenia | {safe_int(account_summary.get('impressions'))} |",
            f"| Konwersje | {safe_float(account_summary.get('conversions')):.2f} |",
            f"| Wartosc konwersji | {format_money_amount(account_summary.get('conversion_value', 0), currency_code)} |",
            f"| ROAS | {safe_float(account_summary.get('roas')):.2f} |",
            f"| CPA | {format_money_amount(account_summary.get('cpa', 0), currency_code)} |",
            "",
        ]
    )
    if plan.warnings:
        lines.extend(["## Uwagi", ""])
        lines.extend(f"- {md_cell(item)}" for item in plan.warnings)
        lines.append("")
    lines.extend(["## Obszary audytu AI", "", "| Obszar | Pytanie audytowe |", "| --- | --- |"])
    for row in plan.audit_brief:
        lines.append(f"| {md_cell(row.get('area', ''))} | {md_cell(row.get('question', ''))} |")
    lines.append("")
    if plan.conversation_questions:
        lines.extend(["## Pytania do rozmowy", ""])
        lines.extend(f"- {md_cell(item)}" for item in plan.conversation_questions)
        lines.append("")
    top_campaigns = plan.summaries.get("top_campaigns_by_cost", [])
    if top_campaigns:
        lines.extend(
            [
                "## Top kampanie po koszcie",
                "",
                "| Kampania | Typ | Status | Budzet dzienny | Koszt | Konwersje | Wartosc | ROAS |",
                "| --- | --- | --- | ---: | ---: | ---: | ---: | ---: |",
            ]
        )
        for campaign in top_campaigns[:20]:
            lines.append(
                f"| {md_cell(campaign.get('campaign_name', ''))} | {campaign.get('channel_type', '')} | "
                f"{campaign.get('status', '')} | {format_money_micros(campaign.get('daily_budget_micros', 0), currency_code)} | "
                f"{format_money_micros(campaign.get('cost_micros', 0), currency_code)} | "
                f"{safe_float(campaign.get('conversions')):.2f} | "
                f"{format_money_amount(campaign.get('conversion_value', 0), currency_code)} | "
                f"{safe_float(campaign.get('roas')):.2f} |"
            )
        lines.append("")
    lines.extend(
        [
            "## Szablon planu po rozmowie",
            "",
            "Agent AI uzupelnia te sekcje po analizie JSON i rozmowie z uzytkownikiem:",
            "",
            "- Cel strategiczny",
            "- Co dziala dobrze",
            "- Co poprawiamy",
            "- Kampanie do dodania, ograniczenia, skalowania albo zamkniecia",
            "- Dzialania dla feedu, produktow, Search, pomiaru i budzetow",
            "- Plan na 7, 30 i 90 dni",
            "- Metryki sukcesu i ryzyka",
            "",
            "Pelny pakiet danych i pole `future_plan` sa w pliku JSON.",
            "",
        ]
    )
    if plan.knowledge_rules:
        lines.extend(["## Reguly z bazy wiedzy", "", "| ID | Temat | Rekomendacja | Ryzyko |", "| --- | --- | --- | --- |"])
        for rule in plan.knowledge_rules:
            lines.append(
                f"| {md_cell(rule.get('id', ''))} | {md_cell(rule.get('topic', ''))} | "
                f"{md_cell(rule.get('recommendation', ''))} | {md_cell(rule.get('risk', ''))} |"
            )
        lines.append("")
    md_path.write_text("\n".join(lines), encoding="utf-8")
    return json_path, md_path


def print_ai_account_audit_plan(plan: AiAccountAuditPlan) -> None:
    currency_code = plan.account.get("currency_code", "")
    account_summary = plan.summaries.get("account_30d", {})
    counts = plan.summaries.get("dataset_counts", {})
    print("\nPlan audytu AI calego konta")
    print_table(
        ["Metryka", "Wartosc"],
        [
            ["Zakres danych", plan.date_range],
            ["Zbiory danych", str(len(counts))],
            ["Kampanie", str(counts.get("campaigns", 0))],
            ["Grupy reklam", str(counts.get("ad_groups", 0))],
            ["Reklamy", str(counts.get("ads", 0))],
            ["Slowa kluczowe", str(counts.get("keywords", 0))],
            ["Zapytania", str(counts.get("search_terms", 0))],
            ["Produkty Shopping", str(counts.get("shopping_products", 0))],
            ["Zmiany do wdrozenia", "0"],
        ],
    )
    print("\nPodsumowanie konta 30 dni")
    print_table(
        ["Metryka", "Wartosc"],
        [
            ["Koszt", format_money_micros(account_summary.get("cost_micros", 0), currency_code)],
            ["Klikniecia", str(safe_int(account_summary.get("clicks")))],
            ["Wyswietlenia", str(safe_int(account_summary.get("impressions")))],
            ["Konwersje", f"{safe_float(account_summary.get('conversions')):.2f}"],
            ["Wartosc konwersji", format_money_amount(account_summary.get("conversion_value", 0), currency_code)],
            ["ROAS", f"{safe_float(account_summary.get('roas')):.2f}"],
            ["CPA", format_money_amount(account_summary.get("cpa", 0), currency_code)],
        ],
    )
    if plan.warnings:
        print("\nUwagi")
        print_table(["Nr", "Uwaga"], [[str(index), item] for index, item in enumerate(plan.warnings, 1)])
    print("\nZakres rozmowy audytowej")
    print_table(
        ["Nr", "Obszar", "Pytanie"],
        [[str(index), row["area"], row["question"]] for index, row in enumerate(plan.audit_brief, 1)],
    )
    top_campaigns = plan.summaries.get("top_campaigns_by_cost", [])
    if top_campaigns:
        print("\nTop kampanie po koszcie")
        print_table(
            ["Nr", "Kampania", "Typ", "Koszt", "Konw.", "Wartosc", "ROAS"],
            [
                [
                    str(index),
                    campaign.get("campaign_name", ""),
                    campaign.get("channel_type", ""),
                    format_money_micros(campaign.get("cost_micros", 0), currency_code),
                    f"{safe_float(campaign.get('conversions')):.2f}",
                    format_money_amount(campaign.get("conversion_value", 0), currency_code),
                    f"{safe_float(campaign.get('roas')):.2f}",
                ]
                for index, campaign in enumerate(top_campaigns[:20], 1)
            ],
        )
    print("\nNastepny krok dla agenta AI")
    print_table(
        ["Nr", "Dzialanie"],
        [
            ["1", "Przeczytaj pelny JSON z pakietem danych."],
            ["2", "Zrob audyt konta i kazdej waznej kampanii osobno."],
            ["3", "Zadaj uzytkownikowi pytania o cel, priorytety, marze i ryzyko."],
            ["4", "Po rozmowie uzupelnij future_plan albo zapisz osobny plan dalszych dzialan."],
        ],
    )


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 future_plan_item_count(future_plan: dict) -> int:
    total = 0
    for key, value in future_plan.items():
        if key == "status":
            continue
        if isinstance(value, list):
            total += len(value)
        elif isinstance(value, str) and value.strip():
            total += 1
    return total


def apply_ai_account_audit_plan(
    client_config: ClientConfig,
    plan: AiAccountAuditPlan,
    show_navigation: bool = True,
) -> None:
    print("\nTo zadanie jest audytem AI i nie wdraza zmian na koncie Google Ads.")
    changes_path = append_change_markdown(client_config.domain, TASK_NAME, [])
    history_path = append_history(
        client_config.domain,
        {
            "task": TASK_NAME,
            "status": "audyt AI oznaczony jako wykonany",
            "campaign": ", ".join(row.get("campaign_name", "") for row in plan.summaries.get("top_campaigns_by_cost", [])[:10]),
            "summary": {
                "datasets": len(plan.datasets),
                "campaigns": len(plan.datasets.get("campaigns", [])),
                "ad_groups": len(plan.datasets.get("ad_groups", [])),
                "keywords": len(plan.datasets.get("keywords", [])),
                "search_terms": len(plan.datasets.get("search_terms", [])),
                "shopping_products": len(plan.datasets.get("shopping_products", [])),
                "future_plan_items": future_plan_item_count(plan.future_plan),
                "changes": 0,
            },
        },
    )
    print(f"Historia JSONL: {history_path}")
    print(f"Historia Markdown: {changes_path}")
    if show_navigation:
        print_next_navigation(client_config.domain)


def run_ai_account_audit(
    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 AI 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 = AiAccountAuditPlan.from_dict(plan_data)
        print_ai_account_audit_plan(plan)
        apply_ai_account_audit_plan(client_config, plan, show_navigation=show_navigation)
        return

    print(f"\nKlient: {client_config.domain}")
    print("Pobieram pakiet surowych danych do audytu AI konta...")
    plan = build_ai_account_audit_plan(client_config, global_rules)
    print_ai_account_audit_plan(plan)
    json_path, md_path = save_ai_account_audit_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": "pakiet audytu AI przygotowany",
            "campaign": ", ".join(row.get("campaign_name", "") for row in plan.summaries.get("top_campaigns_by_cost", [])[:10]),
            "summary": {
                "datasets": len(plan.datasets),
                "campaigns": len(plan.datasets.get("campaigns", [])),
                "ad_groups": len(plan.datasets.get("ad_groups", [])),
                "keywords": len(plan.datasets.get("keywords", [])),
                "search_terms": len(plan.datasets.get("search_terms", [])),
                "shopping_products": len(plan.datasets.get("shopping_products", [])),
                "changes": 0,
            },
        },
    )

    if plan_only:
        print("\nTryb plan-only: zmiany nie zostaly wdrozone.")
        print("Agent AI powinien teraz przeczytac JSON, przeprowadzic audyt i zapisac future_plan po rozmowie.")
        if show_navigation:
            print_next_navigation(client_config.domain)
        return

    print("\nBrak zmian do wdrozenia. To zadanie przygotowuje pakiet danych do rozmowy audytowej z agentem AI.")
    if show_navigation:
        print_next_navigation(client_config.domain)
