from __future__ import annotations

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

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_device_performance"
TASK_NAME = "Sprawdzenie urzadzen"


SCOPE = [
    {
        "area": "Segment urzadzenia",
        "check": "Porownaj wyniki kampanii z ostatnich 30 dni dla komputerow, telefonow i tabletow.",
    },
    {
        "area": "Rentownosc",
        "check": "Policz koszt, konwersje, wartosc konwersji, ROAS i CPA dla kazdego urzadzenia.",
    },
    {
        "area": "Udzial kosztu",
        "check": "Pokaz, jaka czesc kosztu kampanii przypada na dane urzadzenie.",
    },
    {
        "area": "Sygnaly do oceny",
        "check": "Oznacz urzadzenia z istotnym kosztem i slabymi wynikami albo wyraznie odmienna efektywnoscia.",
    },
]


OUT_OF_SCOPE = [
    "zmiany korekt stawek dla urzadzen",
    "zmiany strategii ustalania stawek",
    "zmiany budzetow kampanii",
    "analiza zapytan uzytkownikow",
    "wdrazanie zmian na koncie Google Ads",
]


@dataclass
class DevicePerformancePlan:
    currency_code: str
    device_summary: list[dict]
    campaign_device_rows: list[dict]
    findings: list[dict]
    scope: list[dict]
    out_of_scope: list[str]
    knowledge_rules: list[dict]
    warnings: list[str]

    def to_dict(self) -> dict:
        return {
            "task": TASK_ID,
            "task_name": TASK_NAME,
            "currency_code": self.currency_code,
            "device_summary": self.device_summary,
            "campaign_device_rows": self.campaign_device_rows,
            "findings": self.findings,
            "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) -> "DevicePerformancePlan":
        return cls(
            currency_code=data.get("currency_code", ""),
            device_summary=data.get("device_summary", []),
            campaign_device_rows=data.get("campaign_device_rows", []),
            findings=data.get("findings", []),
            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 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 format_number(value: int | float, decimals: int = 2) -> str:
    return f"{float(value or 0):.{decimals}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 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 empty_metrics() -> dict:
    return {
        "cost_micros": 0,
        "clicks": 0,
        "impressions": 0,
        "conversions": 0.0,
        "conversion_value": 0.0,
    }


def add_metrics(target: dict, metrics: Any) -> None:
    target["cost_micros"] += int(metrics.cost_micros or 0)
    target["clicks"] += int(metrics.clicks or 0)
    target["impressions"] += int(metrics.impressions or 0)
    target["conversions"] += float(metrics.conversions or 0)
    target["conversion_value"] += float(metrics.conversions_value or 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 fetch_device_rows(client_config: ClientConfig) -> 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,
          segments.device,
          metrics.cost_micros,
          metrics.clicks,
          metrics.impressions,
          metrics.conversions,
          metrics.conversions_value
        FROM campaign
        WHERE campaign.status != 'REMOVED'
          AND segments.date DURING LAST_30_DAYS
        """,
    )

    campaigns: dict[str, dict] = {}
    for row in rows:
        campaign_id = str(row.campaign.id)
        campaign = campaigns.setdefault(
            campaign_id,
            {
                "campaign_id": campaign_id,
                "campaign_name": row.campaign.name,
                "status": enum_name(row.campaign.status),
                "channel_type": enum_name(row.campaign.advertising_channel_type),
                "total": empty_metrics(),
                "devices": defaultdict(empty_metrics),
            },
        )
        add_metrics(campaign["total"], row.metrics)
        add_metrics(campaign["devices"][enum_name(row.segments.device)], row.metrics)

    result = []
    for campaign in campaigns.values():
        total_cost = campaign["total"]["cost_micros"]
        for device, metrics in campaign["devices"].items():
            record = {
                "campaign_id": campaign["campaign_id"],
                "campaign_name": campaign["campaign_name"],
                "status": campaign["status"],
                "channel_type": campaign["channel_type"],
                "device": device,
                "cost_micros": metrics["cost_micros"],
                "clicks": metrics["clicks"],
                "impressions": metrics["impressions"],
                "conversions": round(metrics["conversions"], 2),
                "conversion_value": round(metrics["conversion_value"], 2),
                "roas": roas(metrics["conversion_value"], metrics["cost_micros"]),
                "cpa": cpa(metrics["cost_micros"], metrics["conversions"]),
                "cost_share_percent": percent(metrics["cost_micros"], total_cost),
            }
            record["flags"] = device_flags(record)
            record["severity"] = device_severity(record)
            result.append(record)

    result.sort(
        key=lambda row: (
            {"wysokie": 0, "srednie": 1, "niskie": 2, "ok": 9}.get(row["severity"], 9),
            row["campaign_name"],
            -row["cost_micros"],
            row["device"],
        )
    )
    return currency_code, result


def device_flags(row: dict) -> list[str]:
    flags = []
    if row["cost_micros"] > 0 and row["conversions"] == 0 and row["cost_share_percent"] >= 20:
        flags.append("koszt bez konwersji")
    if row["cost_share_percent"] >= 50 and row["roas"] == 0:
        flags.append("duzy udzial kosztu bez wartosci")
    if row["clicks"] >= 30 and row["conversions"] == 0:
        flags.append("wiele klikniec bez konwersji")
    if row["roas"] > 0 and row["roas"] < 1 and row["cost_share_percent"] >= 20:
        flags.append("niski ROAS przy istotnym koszcie")
    if row["impressions"] == 0:
        flags.append("brak wyswietlen")
    return flags or ["ok"]


def device_severity(row: dict) -> str:
    flags = set(row["flags"])
    if "duzy udzial kosztu bez wartosci" in flags or "wiele klikniec bez konwersji" in flags:
        return "wysokie"
    if "koszt bez konwersji" in flags or "niski ROAS przy istotnym koszcie" in flags:
        return "srednie"
    if flags == {"ok"}:
        return "ok"
    return "niskie"


def build_device_summary(rows: list[dict]) -> list[dict]:
    buckets: dict[str, dict] = defaultdict(empty_metrics)
    total_cost = 0
    for row in rows:
        bucket = buckets[row["device"]]
        bucket["cost_micros"] += row["cost_micros"]
        bucket["clicks"] += row["clicks"]
        bucket["impressions"] += row["impressions"]
        bucket["conversions"] += row["conversions"]
        bucket["conversion_value"] += row["conversion_value"]
        total_cost += row["cost_micros"]

    summary = []
    for device, metrics in buckets.items():
        summary.append(
            {
                "device": device,
                "cost_micros": metrics["cost_micros"],
                "clicks": metrics["clicks"],
                "impressions": metrics["impressions"],
                "conversions": round(metrics["conversions"], 2),
                "conversion_value": round(metrics["conversion_value"], 2),
                "roas": roas(metrics["conversion_value"], metrics["cost_micros"]),
                "cpa": cpa(metrics["cost_micros"], metrics["conversions"]),
                "cost_share_percent": percent(metrics["cost_micros"], total_cost),
            }
        )
    summary.sort(key=lambda row: -row["cost_micros"])
    return summary


def build_findings(rows: list[dict]) -> list[dict]:
    findings = []
    for row in rows:
        if row["flags"] == ["ok"]:
            continue
        findings.append(
            {
                "severity": row["severity"],
                "campaign_name": row["campaign_name"],
                "channel_type": row["channel_type"],
                "device": row["device"],
                "cost_share_percent": row["cost_share_percent"],
                "cost_micros": row["cost_micros"],
                "conversions": row["conversions"],
                "conversion_value": row["conversion_value"],
                "roas": row["roas"],
                "cpa": row["cpa"],
                "flags": row["flags"],
                "recommendation": "sprawdz urzadzenie w kontekscie kampanii przed decyzja o korektach stawek albo strukturze",
            }
        )
    return findings


def build_device_performance_plan(client_config: ClientConfig) -> DevicePerformancePlan:
    warnings = []
    try:
        currency_code, campaign_device_rows = fetch_device_rows(client_config)
    except Exception as exc:
        currency_code = ""
        campaign_device_rows = []
        warnings.append(f"Nie udalo sie pobrac segmentu urzadzen z Google Ads API: {exc}")

    if not campaign_device_rows:
        warnings.append("Nie znaleziono danych wedlug urzadzen z ostatnich 30 dni albo nie udalo sie ich pobrac.")

    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 tego zadania nie przypisano jeszcze regul z bazy wiedzy. "
            "Reguly dotyczace segmentow urzadzen bedziemy dopisywac osobno po akceptacji uzytkownika."
        )

    return DevicePerformancePlan(
        currency_code=currency_code,
        device_summary=build_device_summary(campaign_device_rows),
        campaign_device_rows=campaign_device_rows,
        findings=build_findings(campaign_device_rows),
        scope=SCOPE,
        out_of_scope=OUT_OF_SCOPE,
        knowledge_rules=knowledge_rules,
        warnings=warnings,
    )


def save_device_performance_plan(domain: str, plan: DevicePerformancePlan) -> 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 urzadzen",
        "",
        f"Klient: {domain}",
        f"Utworzono: {ts.isoformat(timespec='seconds')}",
        "",
        "## Podsumowanie",
        "",
        f"- Segmenty urzadzen w kampaniach: {len(plan.campaign_device_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.device_summary:
        lines.extend(
            [
                "## Podsumowanie po urzadzeniach",
                "",
                "| Urzadzenie | Koszt | Udzial kosztu | Klikniecia | Konwersje | Wartosc konwersji | ROAS | CPA |",
                "| --- | --- | --- | --- | --- | --- | --- | --- |",
            ]
        )
        for row in plan.device_summary:
            lines.append(
                f"| {row['device']} | {format_money_micros(row['cost_micros'], plan.currency_code)} | "
                f"{row['cost_share_percent']:.1f}% | {row['clicks']} | {format_number(row['conversions'])} | "
                f"{format_money_amount(row['conversion_value'], plan.currency_code)} | {format_number(row['roas'])} | "
                f"{format_money_amount(row['cpa'], plan.currency_code)} |"
            )
        lines.append("")
    if plan.findings:
        lines.extend(
            [
                "## Elementy do oceny",
                "",
                "| Waznosc | Kampania | Typ | Urzadzenie | Koszt | Udzial kosztu | Konwersje | ROAS | Flagi | Rekomendacja |",
                "| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |",
            ]
        )
        for item in plan.findings:
            lines.append(
                f"| {item['severity']} | {md_cell(item['campaign_name'])} | {item['channel_type']} | {item['device']} | "
                f"{format_money_micros(item['cost_micros'], plan.currency_code)} | {item['cost_share_percent']:.1f}% | "
                f"{format_number(item['conversions'])} | {format_number(item['roas'])} | "
                f"{md_cell(', '.join(item['flags']))} | {md_cell(item['recommendation'])} |"
            )
        lines.append("")
    if plan.campaign_device_rows:
        lines.extend(
            [
                "## Kampanie i urzadzenia",
                "",
                "| Kampania | Typ | Urzadzenie | Koszt | Udzial kosztu | Klikniecia | Konwersje | Wartosc konwersji | ROAS | CPA | Flagi |",
                "| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |",
            ]
        )
        for row in plan.campaign_device_rows:
            lines.append(
                f"| {md_cell(row['campaign_name'])} | {row['channel_type']} | {row['device']} | "
                f"{format_money_micros(row['cost_micros'], plan.currency_code)} | {row['cost_share_percent']:.1f}% | "
                f"{row['clicks']} | {format_number(row['conversions'])} | "
                f"{format_money_amount(row['conversion_value'], plan.currency_code)} | {format_number(row['roas'])} | "
                f"{format_money_amount(row['cpa'], plan.currency_code)} | {md_cell(', '.join(row['flags']))} |"
            )
        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_device_performance_plan(plan: DevicePerformancePlan) -> None:
    print("\nPlan sprawdzenia urzadzen")
    print_table(
        ["Metryka", "Liczba"],
        [
            ["Segmenty urzadzen w kampaniach", str(len(plan.campaign_device_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.device_summary:
        print("\nPodsumowanie po urzadzeniach")
        print_table(
            ["Urzadzenie", "Koszt", "Udzial", "Klikniecia", "Konw.", "Wartosc", "ROAS", "CPA"],
            [
                [
                    row["device"],
                    format_money_micros(row["cost_micros"], plan.currency_code),
                    f"{row['cost_share_percent']:.1f}%",
                    str(row["clicks"]),
                    format_number(row["conversions"]),
                    format_money_amount(row["conversion_value"], plan.currency_code),
                    format_number(row["roas"]),
                    format_money_amount(row["cpa"], plan.currency_code),
                ]
                for row in plan.device_summary
            ],
        )
    if plan.findings:
        print("\nElementy do oceny")
        print_table(
            ["Nr", "Waznosc", "Kampania", "Urzadzenie", "Koszt", "Udzial", "Konw.", "ROAS", "Flagi"],
            [
                [
                    str(index),
                    item["severity"],
                    item["campaign_name"],
                    item["device"],
                    format_money_micros(item["cost_micros"], plan.currency_code),
                    f"{item['cost_share_percent']:.1f}%",
                    format_number(item["conversions"]),
                    format_number(item["roas"]),
                    ", ".join(item["flags"]),
                ]
                for index, item in enumerate(plan.findings[:30], 1)
            ],
        )
        if len(plan.findings) > 30:
            print(f"... oraz {len(plan.findings) - 30} kolejnych elementow w pliku planu")
    if plan.campaign_device_rows:
        print("\nKampanie i urzadzenia")
        print_table(
            ["Nr", "Kampania", "Typ", "Urzadzenie", "Koszt", "Udzial", "Konw.", "ROAS", "Flagi"],
            [
                [
                    str(index),
                    row["campaign_name"],
                    row["channel_type"],
                    row["device"],
                    format_money_micros(row["cost_micros"], plan.currency_code),
                    f"{row['cost_share_percent']:.1f}%",
                    format_number(row["conversions"]),
                    format_number(row["roas"]),
                    ", ".join(row["flags"]),
                ]
                for index, row in enumerate(plan.campaign_device_rows[:30], 1)
            ],
        )
        if len(plan.campaign_device_rows) > 30:
            print(f"... oraz {len(plan.campaign_device_rows) - 30} kolejnych wierszy w pliku planu")
    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_device_performance_plan(
    client_config: ClientConfig,
    plan: DevicePerformancePlan,
    show_navigation: bool = True,
) -> None:
    print("\nTo zadanie jest audytem urzadzen 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 oznaczony jako wykonany",
            "campaign": ", ".join(item["campaign_name"] for item in plan.findings[:10]),
            "summary": {
                "device_rows": len(plan.campaign_device_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_check_device_performance(
    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 = DevicePerformancePlan.from_dict(plan_data)
        print_device_performance_plan(plan)
        apply_device_performance_plan(client_config, plan, show_navigation=show_navigation)
        return

    print(f"\nKlient: {client_config.domain}")
    print("Przygotowuje plan sprawdzenia urzadzen...")
    plan = build_device_performance_plan(client_config)
    print_device_performance_plan(plan)
    json_path, md_path = save_device_performance_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(item["campaign_name"] for item in plan.findings[:10]),
            "summary": {
                "device_rows": len(plan.campaign_device_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 urzadzen.")
    if show_navigation:
        print_next_navigation(client_config.domain)
