from __future__ import annotations

import json
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


OUT_OF_SCOPE_COMMON = [
    "wdrazanie zmian na koncie Google Ads",
    "automatyczne zmiany budzetow",
    "automatyczne zmiany stawek albo strategii ustalania stawek",
    "automatyczne wylaczanie kampanii, grup reklam, slow kluczowych, reklam albo produktow",
]


@dataclass(frozen=True)
class AuditDefinition:
    task_id: str
    task_name: str
    intro: str
    query: str
    row_builder: Callable[[Any], dict]
    summary_fields: list[str]
    table_fields: list[str]
    scope: list[dict]
    out_of_scope: list[str]
    sort_key: Callable[[dict], tuple]
    finding_builder: Callable[[dict], list[str]]


@dataclass
class GenericAuditPlan:
    task: str
    task_name: str
    rows: list[dict]
    findings: list[dict]
    summary: list[dict]
    scope: list[dict]
    out_of_scope: list[str]
    knowledge_rules: list[dict]
    warnings: list[str]

    def to_dict(self) -> dict:
        return {
            "task": self.task,
            "task_name": self.task_name,
            "rows": self.rows,
            "findings": self.findings,
            "summary": self.summary,
            "scope": self.scope,
            "out_of_scope": self.out_of_scope,
            "knowledge_rules": self.knowledge_rules,
            "warnings": self.warnings,
            "changes": [],
        }

    @classmethod
    def from_dict(cls, data: dict) -> "GenericAuditPlan":
        return cls(
            task=data.get("task", ""),
            task_name=data.get("task_name", ""),
            rows=data.get("rows", []),
            findings=data.get("findings", []),
            summary=data.get("summary", []),
            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 md_cell(value: Any) -> str:
    return str(value or "").replace("|", "\\|").replace("\n", " ").strip()


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


def 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 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 decimal(value: int | float, places: int = 2) -> str:
    return f"{float(value or 0):.{places}f}"


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


def rate(numerator: int | float, denominator: int | float) -> float:
    if not denominator:
        return 0.0
    return round((float(numerator) / float(denominator)) * 100, 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 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 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 base_metrics(row: Any, currency_code: str) -> dict:
    metrics = row.metrics
    cost_micros = safe_int(metrics.cost_micros)
    conversions = safe_float(metrics.conversions)
    conversion_value = safe_float(metrics.conversions_value)
    clicks = safe_int(metrics.clicks)
    impressions = safe_int(metrics.impressions)
    return {
        "impressions": impressions,
        "clicks": clicks,
        "cost": money_micros(cost_micros, currency_code),
        "cost_micros": cost_micros,
        "conversions": round(conversions, 2),
        "conversion_value": money_amount(conversion_value, currency_code),
        "conversion_value_raw": round(conversion_value, 2),
        "ctr": f"{rate(clicks, impressions):.2f}%",
        "avg_cpc": money_micros(int(cost_micros / clicks) if clicks else 0, currency_code),
        "conversion_rate": f"{rate(conversions, clicks):.2f}%",
        "roas": roas(conversion_value, cost_micros),
        "cpa": money_amount(cpa(cost_micros, conversions), currency_code),
    }


def row_campaign_day(row: Any, currency_code: str) -> dict:
    return {
        "campaign": row.campaign.name,
        "channel": enum_name(row.campaign.advertising_channel_type),
        "day_of_week": enum_name(row.segments.day_of_week),
        **base_metrics(row, currency_code),
    }


def row_campaign_hour(row: Any, currency_code: str) -> dict:
    return {
        "campaign": row.campaign.name,
        "channel": enum_name(row.campaign.advertising_channel_type),
        "hour": str(row.segments.hour),
        **base_metrics(row, currency_code),
    }


def row_campaign_network(row: Any, currency_code: str) -> dict:
    return {
        "campaign": row.campaign.name,
        "channel": enum_name(row.campaign.advertising_channel_type),
        "network": enum_name(row.segments.ad_network_type),
        **base_metrics(row, currency_code),
    }


def row_ad_group(row: Any, currency_code: str) -> dict:
    return {
        "campaign": row.campaign.name,
        "ad_group": row.ad_group.name,
        "status": enum_name(row.ad_group.status),
        "type": enum_name(row.ad_group.type_),
        **base_metrics(row, currency_code),
    }


def row_keyword_quality(row: Any, currency_code: str) -> dict:
    criterion = row.ad_group_criterion
    quality = criterion.quality_info
    return {
        "campaign": row.campaign.name,
        "ad_group": row.ad_group.name,
        "keyword": criterion.keyword.text,
        "match_type": enum_name(criterion.keyword.match_type),
        "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),
        **base_metrics(row, currency_code),
    }


def row_landing_page(row: Any, currency_code: str) -> dict:
    return {
        "landing_page": row.landing_page_view.unexpanded_final_url,
        **base_metrics(row, currency_code),
    }


def row_conversion_action(row: Any, currency_code: str) -> dict:
    return {
        "campaign": row.campaign.name,
        "conversion_action": str(row.segments.conversion_action_name or ""),
        "conversion_category": enum_name(row.segments.conversion_action_category),
        **base_metrics(row, currency_code),
    }


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


def row_gender(row: Any, currency_code: str) -> dict:
    return {
        "campaign": row.campaign.name,
        "ad_group": row.ad_group.name,
        "gender": enum_name(row.ad_group_criterion.gender.type_),
        **base_metrics(row, currency_code),
    }


def row_age_range(row: Any, currency_code: str) -> dict:
    return {
        "campaign": row.campaign.name,
        "ad_group": row.ad_group.name,
        "age_range": enum_name(row.ad_group_criterion.age_range.type_),
        **base_metrics(row, currency_code),
    }


def generic_findings(row: dict) -> list[str]:
    flags = []
    if row.get("cost_micros", 0) >= 100_000_000 and row.get("conversions", 0) == 0:
        flags.append("istotny koszt bez konwersji")
    if row.get("clicks", 0) >= 50 and row.get("conversions", 0) == 0:
        flags.append("wiele klikniec bez konwersji")
    if row.get("roas", 0) > 0 and row.get("roas", 0) < 1 and row.get("cost_micros", 0) >= 100_000_000:
        flags.append("niski ROAS przy istotnym koszcie")
    if row.get("impressions", 0) >= 1000 and row.get("clicks", 0) == 0:
        flags.append("wyswietlenia bez klikniec")
    return flags


def quality_findings(row: dict) -> list[str]:
    flags = generic_findings(row)
    if 0 < row.get("quality_score", 0) <= 3:
        flags.append("niski Wynik Jakosci")
    if row.get("landing_page_quality") in {"BELOW_AVERAGE", "BELOW AVERAGE"}:
        flags.append("slaba jakosc strony docelowej")
    if row.get("expected_ctr") in {"BELOW_AVERAGE", "BELOW AVERAGE"}:
        flags.append("niski przewidywany CTR")
    return flags


def landing_page_findings(row: dict) -> list[str]:
    flags = generic_findings(row)
    if not row.get("landing_page"):
        flags.append("brak widocznego URL strony docelowej")
    return flags


def build_summary(rows: list[dict], summary_fields: list[str], currency_code: str) -> list[dict]:
    summary = []
    for field in summary_fields:
        buckets: dict[str, dict] = {}
        for row in rows:
            key = str(row.get(field, "") or "(brak)")
            bucket = buckets.setdefault(
                key,
                {
                    "segment": key,
                    "rows": 0,
                    "cost_micros": 0,
                    "clicks": 0,
                    "impressions": 0,
                    "conversions": 0.0,
                    "conversion_value_raw": 0.0,
                },
            )
            bucket["rows"] += 1
            bucket["cost_micros"] += safe_int(row.get("cost_micros"))
            bucket["clicks"] += safe_int(row.get("clicks"))
            bucket["impressions"] += safe_int(row.get("impressions"))
            bucket["conversions"] += safe_float(row.get("conversions"))
            bucket["conversion_value_raw"] += safe_float(row.get("conversion_value_raw"))
        for bucket in buckets.values():
            summary.append(
                {
                    "group_by": field,
                    "segment": bucket["segment"],
                    "rows": bucket["rows"],
                    "cost": money_micros(bucket["cost_micros"], currency_code),
                    "clicks": bucket["clicks"],
                    "conversions": round(bucket["conversions"], 2),
                    "conversion_value": money_amount(bucket["conversion_value_raw"], currency_code),
                    "roas": roas(bucket["conversion_value_raw"], bucket["cost_micros"]),
                }
            )
    summary.sort(key=lambda row: (row["group_by"], -safe_float(str(row["cost"]).split()[0].replace(",", "."))))
    return summary


def build_plan(definition: AuditDefinition, client_config: ClientConfig) -> GenericAuditPlan:
    warnings = []
    currency_code = ""
    rows: list[dict] = []
    try:
        google_client = get_google_ads_client(use_proto_plus=True)
        currency_code = fetch_currency_code(google_client, client_config.safe_customer_id)
        api_rows = run_query(google_client, client_config.safe_customer_id, definition.query)
        rows = [definition.row_builder(row, currency_code) for row in api_rows]
    except Exception as exc:
        warnings.append(f"Nie udalo sie pobrac danych dla zadania {definition.task_name}: {compact_error(exc)}")

    rows.sort(key=definition.sort_key)
    findings = []
    for row in rows:
        flags = definition.finding_builder(row)
        if flags:
            findings.append(
                {
                    **{key: row.get(key, "") for key in definition.table_fields if key in row},
                    "flags": flags,
                    "recommendation": "sprawdz ten segment przed decyzja optymalizacyjna",
                }
            )

    if not rows:
        warnings.append("Nie znaleziono danych w analizowanym zakresie albo API nie zwrocilo wynikow.")

    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(definition.task_id)
    ]
    if not knowledge_rules:
        warnings.append(
            "Do tego zadania nie przypisano jeszcze regul z bazy wiedzy. "
            "Reguly bedziemy dopisywac osobno po akceptacji uzytkownika."
        )

    return GenericAuditPlan(
        task=definition.task_id,
        task_name=definition.task_name,
        rows=rows,
        findings=findings,
        summary=build_summary(rows, definition.summary_fields, currency_code),
        scope=definition.scope,
        out_of_scope=definition.out_of_scope,
        knowledge_rules=knowledge_rules,
        warnings=warnings,
    )


def compact_error(exc: Exception) -> str:
    message = str(exc)
    if "PROHIBITED" in message:
        return "Google Ads API odrzucilo kombinacje pol w zapytaniu. Szczegoly sa w logu requestu."
    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 save_plan(domain: str, definition: AuditDefinition, plan: GenericAuditPlan) -> 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')}_{definition.task_id}"
    json_path = base / f"{stem}.json"
    md_path = base / f"{stem}.md"
    payload = {
        "created_at": ts.isoformat(timespec="seconds"),
        "client": domain,
        **plan.to_dict(),
    }
    json_path.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")

    lines = [
        f"# Plan: {definition.task_name}",
        "",
        f"Klient: {domain}",
        f"Utworzono: {ts.isoformat(timespec='seconds')}",
        "",
        "## Podsumowanie",
        "",
        f"- Wiersze danych: {len(plan.rows)}",
        f"- Elementy do oceny: {len(plan.findings)}",
        f"- Reguly wiedzy przypisane do zadania: {len(plan.knowledge_rules)}",
        "- Zmiany do wdrozenia: 0",
        "",
    ]
    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"| {md_cell(row.get('area', ''))} | {md_cell(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.summary:
        lines.extend(["## Podsumowanie segmentow", "", "| Grupowanie | Segment | Wiersze | Koszt | Klikniecia | Konwersje | Wartosc | ROAS |", "| --- | --- | --- | --- | --- | --- | --- | --- |"])
        for row in plan.summary:
            lines.append(
                f"| {md_cell(row['group_by'])} | {md_cell(row['segment'])} | {row['rows']} | {row['cost']} | "
                f"{row['clicks']} | {row['conversions']} | {row['conversion_value']} | {row['roas']} |"
            )
        lines.append("")
    if plan.findings:
        fields = [field for field in definition.table_fields if any(field in row for row in plan.findings)]
        lines.extend(["## Elementy do oceny", "", "| " + " | ".join(fields + ["Flagi", "Rekomendacja"]) + " |", "| " + " | ".join("---" for _ in fields + ["Flagi", "Rekomendacja"]) + " |"])
        for row in plan.findings:
            values = [md_cell(row.get(field, "")) for field in fields]
            values.extend([md_cell(", ".join(row.get("flags", []))), md_cell(row.get("recommendation", ""))])
            lines.append("| " + " | ".join(values) + " |")
        lines.append("")
    if plan.rows:
        fields = [field for field in definition.table_fields if any(field in row for row in plan.rows)]
        lines.extend(["## Dane", "", "| " + " | ".join(fields) + " |", "| " + " | ".join("---" for _ in fields) + " |"])
        for row in plan.rows:
            lines.append("| " + " | ".join(md_cell(row.get(field, "")) for field in fields) + " |")
        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"| {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_plan(definition: AuditDefinition, plan: GenericAuditPlan) -> None:
    print(f"\nPlan: {definition.task_name}")
    print_table(
        ["Metryka", "Liczba"],
        [
            ["Wiersze danych", str(len(plan.rows))],
            ["Elementy do oceny", str(len(plan.findings))],
            ["Reguly wiedzy", str(len(plan.knowledge_rules))],
            ["Zmiany do wdrozenia", "0"],
        ],
    )
    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.summary:
        print("\nPodsumowanie segmentow")
        print_table(
            ["Grupowanie", "Segment", "Wiersze", "Koszt", "Klikniecia", "Konw.", "Wartosc", "ROAS"],
            [
                [row["group_by"], row["segment"], str(row["rows"]), row["cost"], str(row["clicks"]), str(row["conversions"]), row["conversion_value"], str(row["roas"])]
                for row in plan.summary[:30]
            ],
        )
        if len(plan.summary) > 30:
            print(f"... oraz {len(plan.summary) - 30} kolejnych segmentow w pliku planu")
    if plan.findings:
        fields = [field for field in definition.table_fields if any(field in row for row in plan.findings)]
        rows = []
        for index, row in enumerate(plan.findings[:30], 1):
            rows.append([str(index)] + [row.get(field, "") for field in fields] + [", ".join(row.get("flags", []))])
        print("\nElementy do oceny")
        print_table(["Nr"] + fields + ["Flagi"], rows)
        if len(plan.findings) > 30:
            print(f"... oraz {len(plan.findings) - 30} kolejnych elementow w pliku planu")
    elif plan.rows:
        fields = [field for field in definition.table_fields if any(field in row for row in plan.rows)]
        print("\nDane")
        print_table(
            ["Nr"] + fields,
            [[str(index)] + [row.get(field, "") for field in fields] for index, row in enumerate(plan.rows[:30], 1)],
        )
        if len(plan.rows) > 30:
            print(f"... oraz {len(plan.rows) - 30} kolejnych wierszy w pliku planu")


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_plan(client_config: ClientConfig, plan: GenericAuditPlan, show_navigation: bool = True) -> None:
    print(f"\nTo zadanie jest audytem: {plan.task_name}. Nie wdraza zmian na koncie Google Ads.")
    changes_path = append_change_markdown(client_config.domain, plan.task_name, [])
    history_path = append_history(
        client_config.domain,
        {
            "task": plan.task_name,
            "status": "audyt oznaczony jako wykonany",
            "summary": {
                "rows": len(plan.rows),
                "findings": len(plan.findings),
                "knowledge_rules": len(plan.knowledge_rules),
                "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_generic_audit(
    definition: AuditDefinition,
    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 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 = GenericAuditPlan.from_dict(plan_data)
        print_plan(definition, plan)
        apply_plan(client_config, plan, show_navigation=show_navigation)
        return

    print(f"\nKlient: {client_config.domain}")
    print(definition.intro)
    plan = build_plan(definition, client_config)
    print_plan(definition, plan)
    json_path, md_path = save_plan(client_config.domain, definition, plan)
    print(f"\nPlan JSON: {json_path}")
    print(f"Plan Markdown: {md_path}")
    append_history(
        client_config.domain,
        {
            "task": definition.task_name,
            "status": "plan przygotowany",
            "summary": {
                "rows": len(plan.rows),
                "findings": len(plan.findings),
                "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.")
    if show_navigation:
        print_next_navigation(client_config.domain)


def scope(*items: tuple[str, str]) -> list[dict]:
    return [{"area": area, "check": check} for area, check in items]


def query_campaign_segment(segment_field: str) -> str:
    return f"""
        SELECT
          campaign.id,
          campaign.name,
          campaign.status,
          campaign.advertising_channel_type,
          {segment_field},
          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
        """


QUERY_AD_GROUP = """
        SELECT
          campaign.name,
          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
        """


QUERY_KEYWORD_QUALITY = """
        SELECT
          campaign.name,
          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
        """


QUERY_LANDING_PAGE = """
        SELECT
          landing_page_view.unexpanded_final_url,
          metrics.impressions,
          metrics.clicks,
          metrics.cost_micros,
          metrics.conversions,
          metrics.conversions_value
        FROM landing_page_view
        WHERE segments.date DURING LAST_30_DAYS
        """


QUERY_CONVERSION_ACTION = """
        SELECT
          campaign.name,
          segments.conversion_action_name,
          segments.conversion_action_category,
          metrics.conversions,
          metrics.conversions_value
        FROM campaign
        WHERE campaign.status != 'REMOVED'
          AND segments.date DURING LAST_30_DAYS
        """


QUERY_SHOPPING_PRODUCT = """
        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
        LIMIT 500
        """


QUERY_GENDER = """
        SELECT
          campaign.name,
          ad_group.name,
          ad_group_criterion.gender.type,
          metrics.impressions,
          metrics.clicks,
          metrics.cost_micros,
          metrics.conversions,
          metrics.conversions_value
        FROM gender_view
        WHERE segments.date DURING LAST_30_DAYS
        """


QUERY_AGE_RANGE = """
        SELECT
          campaign.name,
          ad_group.name,
          ad_group_criterion.age_range.type,
          metrics.impressions,
          metrics.clicks,
          metrics.cost_micros,
          metrics.conversions,
          metrics.conversions_value
        FROM age_range_view
        WHERE segments.date DURING LAST_30_DAYS
        """


DEFINITIONS: dict[str, AuditDefinition] = {
    "check_day_of_week_performance": AuditDefinition(
        task_id="check_day_of_week_performance",
        task_name="Sprawdzenie dni tygodnia",
        intro="Przygotowuje plan sprawdzenia wynikow wedlug dni tygodnia...",
        query=query_campaign_segment("segments.day_of_week"),
        row_builder=row_campaign_day,
        summary_fields=["day_of_week"],
        table_fields=["campaign", "channel", "day_of_week", "cost", "clicks", "conversions", "conversion_value", "roas", "cpa", "ctr"],
        scope=scope(
            ("Dni tygodnia", "Porownaj koszt, klikniecia, konwersje, wartosc konwersji, ROAS i CPA wedlug dnia tygodnia."),
            ("Kontekst kampanii", "Pokaz dane w podziale na kampanie, aby nie mieszac roznych typow ruchu."),
            ("Decyzja reczna", "Zaznacz segmenty do oceny bez zmiany harmonogramu reklam."),
        ),
        out_of_scope=OUT_OF_SCOPE_COMMON + ["zmiany harmonogramu reklam"],
        sort_key=lambda row: (row["campaign"], row["day_of_week"]),
        finding_builder=generic_findings,
    ),
    "check_hour_of_day_performance": AuditDefinition(
        task_id="check_hour_of_day_performance",
        task_name="Sprawdzenie godzin dnia",
        intro="Przygotowuje plan sprawdzenia wynikow wedlug godzin dnia...",
        query=query_campaign_segment("segments.hour"),
        row_builder=row_campaign_hour,
        summary_fields=["hour"],
        table_fields=["campaign", "channel", "hour", "cost", "clicks", "conversions", "conversion_value", "roas", "cpa", "ctr"],
        scope=scope(
            ("Godziny dnia", "Porownaj wyniki wedlug godzin z ostatnich 30 dni."),
            ("Efektywnosc", "Pokaz ROAS, CPA i konwersje dla godzin, w ktorych realnie wydawany jest budzet."),
            ("Granica zadania", "Nie zmieniaj harmonogramu reklam ani stawek godzinowych w tym zadaniu."),
        ),
        out_of_scope=OUT_OF_SCOPE_COMMON + ["zmiany harmonogramu reklam"],
        sort_key=lambda row: (safe_int(row["hour"]), row["campaign"]),
        finding_builder=generic_findings,
    ),
    "check_network_performance": AuditDefinition(
        task_id="check_network_performance",
        task_name="Sprawdzenie efektywnosci sieci",
        intro="Przygotowuje plan sprawdzenia wynikow wedlug sieci...",
        query=query_campaign_segment("segments.ad_network_type"),
        row_builder=row_campaign_network,
        summary_fields=["network", "channel"],
        table_fields=["campaign", "channel", "network", "cost", "clicks", "conversions", "conversion_value", "roas", "cpa", "ctr"],
        scope=scope(
            ("Sieci", "Porownaj wyniki Google Search, partnerow, Display, Shopping i innych sieci zwracanych przez API."),
            ("Jakosc ruchu", "Oznacz sieci z kosztem bez konwersji albo slabym ROAS."),
            ("Granica zadania", "Nie zmieniaj ustawien sieci w tym audycie."),
        ),
        out_of_scope=OUT_OF_SCOPE_COMMON + ["zmiany ustawien sieci kampanii"],
        sort_key=lambda row: (row["campaign"], row["network"]),
        finding_builder=generic_findings,
    ),
    "check_ad_group_performance": AuditDefinition(
        task_id="check_ad_group_performance",
        task_name="Sprawdzenie grup reklam",
        intro="Przygotowuje plan sprawdzenia wynikow grup reklam...",
        query=QUERY_AD_GROUP,
        row_builder=row_ad_group,
        summary_fields=["campaign", "type"],
        table_fields=["campaign", "ad_group", "status", "type", "cost", "clicks", "conversions", "conversion_value", "roas", "cpa", "ctr"],
        scope=scope(
            ("Grupy reklam", "Pokaz koszt, konwersje, wartosc, ROAS i CPA na poziomie grupy reklam."),
            ("Priorytet oceny", "Oznacz grupy z kosztem bez konwersji, wieloma kliknieciami bez konwersji albo slabym ROAS."),
            ("Granica zadania", "Nie wstrzymuj grup reklam automatycznie."),
        ),
        out_of_scope=OUT_OF_SCOPE_COMMON + ["zmiany struktury grup reklam"],
        sort_key=lambda row: (-row["cost_micros"], row["campaign"], row["ad_group"]),
        finding_builder=generic_findings,
    ),
    "check_keyword_quality_score": AuditDefinition(
        task_id="check_keyword_quality_score",
        task_name="Sprawdzenie Wyniku Jakosci slow kluczowych",
        intro="Przygotowuje plan sprawdzenia Wyniku Jakosci slow kluczowych...",
        query=QUERY_KEYWORD_QUALITY,
        row_builder=row_keyword_quality,
        summary_fields=["quality_score", "match_type"],
        table_fields=["campaign", "ad_group", "keyword", "match_type", "quality_score", "creative_quality", "landing_page_quality", "expected_ctr", "cost", "clicks", "conversions", "roas"],
        scope=scope(
            ("Wynik Jakosci", "Sprawdz quality score oraz skladowe: reklama, strona docelowa i przewidywany CTR."),
            ("Koszt i wynik", "Polacz ocene jakosci z kosztem, kliknieciami, konwersjami i ROAS."),
            ("Granica zadania", "Nie pauzuj slow kluczowych i nie zmieniaj stawek."),
        ),
        out_of_scope=OUT_OF_SCOPE_COMMON + ["dodawanie albo usuwanie slow kluczowych"],
        sort_key=lambda row: (row.get("quality_score", 0) or 99, -row["cost_micros"], row["keyword"]),
        finding_builder=quality_findings,
    ),
    "check_landing_page_performance": AuditDefinition(
        task_id="check_landing_page_performance",
        task_name="Sprawdzenie stron docelowych",
        intro="Przygotowuje plan sprawdzenia stron docelowych...",
        query=QUERY_LANDING_PAGE,
        row_builder=row_landing_page,
        summary_fields=[],
        table_fields=["landing_page", "cost", "clicks", "conversions", "conversion_value", "roas", "cpa", "ctr", "conversion_rate"],
        scope=scope(
            ("Strony docelowe", "Pokaz wyniki URL-i docelowych z ostatnich 30 dni."),
            ("Efektywnosc", "Oznacz strony z kosztem, kliknieciami i brakiem konwersji albo niskim ROAS."),
            ("Granica zadania", "Nie zmieniaj URL-i ani reklam automatycznie."),
        ),
        out_of_scope=OUT_OF_SCOPE_COMMON + ["zmiany URL-i docelowych"],
        sort_key=lambda row: (-row["cost_micros"], row["landing_page"]),
        finding_builder=landing_page_findings,
    ),
    "check_conversion_action_performance": AuditDefinition(
        task_id="check_conversion_action_performance",
        task_name="Sprawdzenie akcji konwersji",
        intro="Przygotowuje plan sprawdzenia akcji konwersji...",
        query=QUERY_CONVERSION_ACTION,
        row_builder=row_conversion_action,
        summary_fields=["conversion_action", "conversion_category"],
        table_fields=["campaign", "conversion_action", "conversion_category", "cost", "clicks", "conversions", "conversion_value", "roas", "cpa"],
        scope=scope(
            ("Akcje konwersji", "Pokaz, ktore akcje konwersji generuja wynik w kampaniach."),
            ("Jakosc pomiaru", "Pomoz wychwycic kampanie optymalizowane na niewlasciwy albo podejrzany typ konwersji."),
            ("Granica zadania", "Nie zmieniaj ustawien konwersji ani celow kampanii."),
        ),
        out_of_scope=OUT_OF_SCOPE_COMMON + ["zmiany ustawien konwersji"],
        sort_key=lambda row: (row["campaign"], row["conversion_action"]),
        finding_builder=generic_findings,
    ),
    "check_shopping_product_performance": AuditDefinition(
        task_id="check_shopping_product_performance",
        task_name="Sprawdzenie wynikow produktow Shopping",
        intro="Przygotowuje plan sprawdzenia wynikow produktow Shopping...",
        query=QUERY_SHOPPING_PRODUCT,
        row_builder=row_shopping_product,
        summary_fields=["brand", "category_l1"],
        table_fields=["campaign", "ad_group", "product_title", "item_id", "brand", "category_l1", "cost", "clicks", "conversions", "conversion_value", "roas", "cpa"],
        scope=scope(
            ("Produkty", "Pokaz produkty z kosztem, kliknieciami, konwersjami, wartoscia i ROAS."),
            ("Priorytet feedu", "Wskaz produkty, ktore moga wymagac analizy ceny, tytulu, feedu albo dostepnosci."),
            ("Granica zadania", "Nie zmieniaj feedu, stawek ani struktury kampanii."),
        ),
        out_of_scope=OUT_OF_SCOPE_COMMON + ["optymalizacja tytulow, kategorii Google i unit pricing"],
        sort_key=lambda row: (-row["cost_micros"], row["product_title"]),
        finding_builder=generic_findings,
    ),
    "check_gender_performance": AuditDefinition(
        task_id="check_gender_performance",
        task_name="Sprawdzenie plci odbiorcow",
        intro="Przygotowuje plan sprawdzenia wynikow wedlug plci odbiorcow...",
        query=QUERY_GENDER,
        row_builder=row_gender,
        summary_fields=["gender"],
        table_fields=["campaign", "ad_group", "gender", "cost", "clicks", "conversions", "conversion_value", "roas", "cpa", "ctr"],
        scope=scope(
            ("Plec odbiorcow", "Porownaj wyniki segmentow plci z ostatnich 30 dni."),
            ("Efektywnosc", "Pokaz koszt, konwersje, wartosc, ROAS i CPA."),
            ("Granica zadania", "Nie dodawaj wykluczen demograficznych ani korekt stawek."),
        ),
        out_of_scope=OUT_OF_SCOPE_COMMON + ["zmiany kierowania demograficznego"],
        sort_key=lambda row: (row["campaign"], row["gender"]),
        finding_builder=generic_findings,
    ),
    "check_age_performance": AuditDefinition(
        task_id="check_age_performance",
        task_name="Sprawdzenie wieku odbiorcow",
        intro="Przygotowuje plan sprawdzenia wynikow wedlug wieku odbiorcow...",
        query=QUERY_AGE_RANGE,
        row_builder=row_age_range,
        summary_fields=["age_range"],
        table_fields=["campaign", "ad_group", "age_range", "cost", "clicks", "conversions", "conversion_value", "roas", "cpa", "ctr"],
        scope=scope(
            ("Wiek odbiorcow", "Porownaj wyniki przedzialow wieku z ostatnich 30 dni."),
            ("Efektywnosc", "Pokaz koszt, konwersje, wartosc, ROAS i CPA."),
            ("Granica zadania", "Nie dodawaj wykluczen demograficznych ani korekt stawek."),
        ),
        out_of_scope=OUT_OF_SCOPE_COMMON + ["zmiany kierowania demograficznego"],
        sort_key=lambda row: (row["campaign"], row["age_range"]),
        finding_builder=generic_findings,
    ),
}


def run_check_day_of_week_performance(*args, **kwargs) -> None:
    run_generic_audit(DEFINITIONS["check_day_of_week_performance"], *args, **kwargs)


def run_check_hour_of_day_performance(*args, **kwargs) -> None:
    run_generic_audit(DEFINITIONS["check_hour_of_day_performance"], *args, **kwargs)


def run_check_network_performance(*args, **kwargs) -> None:
    run_generic_audit(DEFINITIONS["check_network_performance"], *args, **kwargs)


def run_check_ad_group_performance(*args, **kwargs) -> None:
    run_generic_audit(DEFINITIONS["check_ad_group_performance"], *args, **kwargs)


def run_check_keyword_quality_score(*args, **kwargs) -> None:
    run_generic_audit(DEFINITIONS["check_keyword_quality_score"], *args, **kwargs)


def run_check_landing_page_performance(*args, **kwargs) -> None:
    run_generic_audit(DEFINITIONS["check_landing_page_performance"], *args, **kwargs)


def run_check_conversion_action_performance(*args, **kwargs) -> None:
    run_generic_audit(DEFINITIONS["check_conversion_action_performance"], *args, **kwargs)


def run_check_shopping_product_performance(*args, **kwargs) -> None:
    run_generic_audit(DEFINITIONS["check_shopping_product_performance"], *args, **kwargs)


def run_check_gender_performance(*args, **kwargs) -> None:
    run_generic_audit(DEFINITIONS["check_gender_performance"], *args, **kwargs)


def run_check_age_performance(*args, **kwargs) -> None:
    run_generic_audit(DEFINITIONS["check_age_performance"], *args, **kwargs)
