from __future__ import annotations

import json
from collections import Counter
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_shopping_product_statuses"
TASK_NAME = "Sprawdzenie statusow produktow Shopping"
DEFAULT_PRODUCT_LIMIT = 500


SCOPE = [
    {
        "area": "Status produktu",
        "check": "Pobierz statusy produktow z zasobu shopping_product w Google Ads API.",
    },
    {
        "area": "Emisja 30 dni",
        "check": "Porownaj produkty ze statusem z danymi emisji z shopping_performance_view z ostatnich 30 dni.",
    },
    {
        "area": "Produkty do oceny",
        "check": "Oznacz produkty niekwalifikujace sie albo kwalifikujace sie, ale bez wyswietlen w ostatnich 30 dniach.",
    },
    {
        "area": "Granica zadania",
        "check": "Oddziel status i emisje produktu od optymalizacji tytulow, kategorii Google, unit pricing i napraw feedu.",
    },
]


OUT_OF_SCOPE = [
    "optymalizacja tytulow produktow",
    "wybor kategorii Google",
    "uzupelnianie unit pricing",
    "naprawa feedu w adsPRO albo Merchant Center",
    "zmiany stawek, budzetow i struktury kampanii Shopping/PMax",
]


PROBLEM_PRODUCT_STATUSES = {
    "DISAPPROVED",
    "NOT_ELIGIBLE",
    "LIMITED",
    "UNKNOWN",
    "UNSPECIFIED",
}


@dataclass
class ShoppingProductStatusPlan:
    product_limit: int
    products: list[dict]
    status_summary: list[dict]
    issue_summary: list[dict]
    problem_items: 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,
            "product_limit": self.product_limit,
            "products": self.products,
            "status_summary": self.status_summary,
            "issue_summary": self.issue_summary,
            "problem_items": self.problem_items,
            "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) -> "ShoppingProductStatusPlan":
        return cls(
            product_limit=int(data.get("product_limit") or DEFAULT_PRODUCT_LIMIT),
            products=data.get("products", []),
            status_summary=data.get("status_summary", []),
            issue_summary=data.get("issue_summary", []),
            problem_items=data.get("problem_items", []),
            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(value: int | float, currency_code: str) -> str:
    suffix = f" {currency_code}" if currency_code else ""
    return f"{micros_to_amount(value):.2f}{suffix}"


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 issue_to_dict(issue: Any) -> dict:
    code = getattr(issue, "code", "")
    severity = getattr(issue, "severity", "")
    resolution = getattr(issue, "resolution", "")
    attribute = getattr(issue, "attribute", "")
    description = getattr(issue, "description", "")
    detail = getattr(issue, "detail", "")
    return {
        "code": enum_name(code),
        "severity": enum_name(severity),
        "resolution": enum_name(resolution),
        "attribute": str(attribute or ""),
        "description": str(description or ""),
        "detail": str(detail or ""),
    }


def fetch_shopping_products(client_config: ClientConfig, limit: int) -> 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,
        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 {int(limit)}
        """,
    )

    products = []
    for row in rows:
        product = row.shopping_product
        issues = [issue_to_dict(issue) for issue in getattr(product, "issues", [])]
        products.append(
            {
                "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),
                "issues": issues,
                "issue_count": len(issues),
                "impressions_30d": 0,
                "clicks_30d": 0,
                "cost_30d_micros": 0,
                "conversions_30d": 0.0,
                "conversion_value_30d": 0.0,
            }
        )
    return currency_code, products


def fetch_product_performance_30d(client_config: ClientConfig) -> dict[str, dict]:
    google_client = get_google_ads_client(use_proto_plus=True)
    rows = run_query(
        google_client,
        client_config.safe_customer_id,
        """
        SELECT
          segments.product_item_id,
          segments.product_title,
          metrics.impressions,
          metrics.clicks,
          metrics.cost_micros,
          metrics.conversions,
          metrics.conversions_value
        FROM shopping_performance_view
        WHERE segments.date DURING LAST_30_DAYS
        LIMIT 10000
        """,
    )

    performance: dict[str, dict] = {}
    for row in rows:
        item_id = str(row.segments.product_item_id or "")
        if not item_id:
            continue
        record = performance.setdefault(
            item_id,
            {
                "item_id": item_id,
                "title": str(row.segments.product_title or ""),
                "impressions_30d": 0,
                "clicks_30d": 0,
                "cost_30d_micros": 0,
                "conversions_30d": 0.0,
                "conversion_value_30d": 0.0,
            },
        )
        record["impressions_30d"] += int(row.metrics.impressions or 0)
        record["clicks_30d"] += int(row.metrics.clicks or 0)
        record["cost_30d_micros"] += int(row.metrics.cost_micros or 0)
        record["conversions_30d"] += float(row.metrics.conversions or 0)
        record["conversion_value_30d"] += float(row.metrics.conversions_value or 0)
    return performance


def product_severity(product: dict) -> str:
    if product["status"] in {"DISAPPROVED", "NOT_ELIGIBLE"}:
        return "wysokie"
    if product["status"] in {"LIMITED", "UNKNOWN", "UNSPECIFIED"}:
        return "srednie"
    if product["issue_count"] > 0:
        return "srednie"
    if product["status"] == "ELIGIBLE" and product["impressions_30d"] == 0:
        return "niskie"
    return "ok"


def product_flags(product: dict) -> list[str]:
    flags = []
    if product["status"] in PROBLEM_PRODUCT_STATUSES:
        flags.append(f"status: {product['status']}")
    if product["issue_count"]:
        flags.append(f"problemy MC/API: {product['issue_count']}")
    if product["status"] == "ELIGIBLE" and product["impressions_30d"] == 0:
        flags.append("brak wyswietlen 30 dni")
    return flags or ["ok"]


def attach_performance(products: list[dict], performance: dict[str, dict]) -> list[dict]:
    for product in products:
        perf = performance.get(product["item_id"], {})
        product["impressions_30d"] = int(perf.get("impressions_30d", 0))
        product["clicks_30d"] = int(perf.get("clicks_30d", 0))
        product["cost_30d_micros"] = int(perf.get("cost_30d_micros", 0))
        product["conversions_30d"] = float(perf.get("conversions_30d", 0.0))
        product["conversion_value_30d"] = float(perf.get("conversion_value_30d", 0.0))
        product["severity"] = product_severity(product)
        product["flags"] = product_flags(product)
    severity_order = {"wysokie": 0, "srednie": 1, "niskie": 2, "ok": 9}
    products.sort(
        key=lambda row: (
            severity_order.get(row["severity"], 9),
            -row["impressions_30d"],
            row["title"],
        )
    )
    return products


def build_status_summary(products: list[dict]) -> list[dict]:
    counter = Counter(product["status"] for product in products)
    return [{"status": key, "count": value} for key, value in counter.most_common()]


def build_issue_summary(products: list[dict]) -> list[dict]:
    counter: Counter[str] = Counter()
    for product in products:
        for issue in product.get("issues", []):
            code = issue.get("code") or "(brak kodu)"
            counter[code] += 1
    return [{"issue_code": key, "count": value} for key, value in counter.most_common()]


def build_problem_items(products: list[dict], currency_code: str) -> list[dict]:
    items = []
    for product in products:
        if product["flags"] == ["ok"]:
            continue
        items.append(
            {
                "severity": product["severity"],
                "item_id": product["item_id"],
                "title": product["title"],
                "status": product["status"],
                "impressions_30d": product["impressions_30d"],
                "clicks_30d": product["clicks_30d"],
                "cost_30d": format_money(product["cost_30d_micros"], currency_code),
                "flags": product["flags"],
                "recommendation": "sprawdz status produktu w Merchant Center albo w diagnostyce produktow Google Ads",
            }
        )
    return items


def build_shopping_product_status_plan(
    client_config: ClientConfig,
    global_rules: dict,
) -> ShoppingProductStatusPlan:
    rules = client_config.effective_rules(global_rules, "shopping_product_statuses")
    limit = int(rules.get("limit", DEFAULT_PRODUCT_LIMIT))
    warnings = []
    currency_code = ""
    try:
        currency_code, products = fetch_shopping_products(client_config, limit)
    except Exception as exc:
        products = []
        warnings.append(f"Nie udalo sie pobrac statusow shopping_product z Google Ads API: {exc}")

    try:
        performance = fetch_product_performance_30d(client_config)
    except Exception as exc:
        performance = {}
        warnings.append(f"Nie udalo sie pobrac emisji produktow z shopping_performance_view: {exc}")

    products = attach_performance(products, performance)
    if not products:
        warnings.append("Nie znaleziono produktow Shopping albo nie udalo sie ich pobrac.")
    if len(products) >= limit:
        warnings.append(f"Pobrano pierwsze {limit} produktow z Google Ads API; zwieksz limit w regulach klienta, jezeli trzeba szerszego audytu.")
    warnings.append(
        "To zadanie sprawdza status i emisje produktu. Naprawy feedu, tytulow, kategorii Google i unit pricing pozostaja w osobnych zadaniach."
    )

    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 statusow produktow Shopping bedziemy dopisywac osobno po akceptacji uzytkownika."
        )

    return ShoppingProductStatusPlan(
        product_limit=limit,
        products=products,
        status_summary=build_status_summary(products),
        issue_summary=build_issue_summary(products),
        problem_items=build_problem_items(products, currency_code),
        scope=SCOPE,
        out_of_scope=OUT_OF_SCOPE,
        knowledge_rules=knowledge_rules,
        warnings=warnings,
    )


def save_shopping_product_status_plan(domain: str, plan: ShoppingProductStatusPlan) -> 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 statusow produktow Shopping",
        "",
        f"Klient: {domain}",
        f"Utworzono: {ts.isoformat(timespec='seconds')}",
        "",
        "## Podsumowanie",
        "",
        f"- Produkty sprawdzone: {len(plan.products)}",
        f"- Elementy do oceny: {len(plan.problem_items)}",
        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.status_summary:
        lines.extend(["## Statusy produktow", "", "| Status | Liczba |", "| --- | --- |"])
        for row in plan.status_summary:
            lines.append(f"| {row['status']} | {row['count']} |")
        lines.append("")
    if plan.issue_summary:
        lines.extend(["## Problemy produktow", "", "| Kod problemu | Liczba |", "| --- | --- |"])
        for row in plan.issue_summary:
            lines.append(f"| {md_cell(row['issue_code'])} | {row['count']} |")
        lines.append("")
    if plan.problem_items:
        lines.extend(
            [
                "## Elementy do oceny",
                "",
                "| Waznosc | Produkt | Tytul | Status | Wyswietlenia 30d | Klikniecia 30d | Koszt 30d | Flagi | Rekomendacja |",
                "| --- | --- | --- | --- | --- | --- | --- | --- | --- |",
            ]
        )
        for item in plan.problem_items:
            lines.append(
                f"| {item['severity']} | {md_cell(item['item_id'])} | {md_cell(item['title'])} | {item['status']} | "
                f"{item['impressions_30d']} | {item['clicks_30d']} | {item['cost_30d']} | "
                f"{md_cell(', '.join(item['flags']))} | {md_cell(item['recommendation'])} |"
            )
        lines.append("")
    if plan.products:
        lines.extend(
            [
                "## Produkty",
                "",
                "| Produkt | Tytul | Status | Jezyk | Feed label | Wyswietlenia 30d | Klikniecia 30d | Flagi |",
                "| --- | --- | --- | --- | --- | --- | --- | --- |",
            ]
        )
        for product in plan.products:
            lines.append(
                f"| {md_cell(product['item_id'])} | {md_cell(product['title'])} | {product['status']} | "
                f"{product['language_code']} | {md_cell(product['feed_label'])} | {product['impressions_30d']} | "
                f"{product['clicks_30d']} | {md_cell(', '.join(product['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_shopping_product_status_plan(plan: ShoppingProductStatusPlan) -> None:
    print("\nPlan sprawdzenia statusow produktow Shopping")
    print_table(
        ["Metryka", "Liczba"],
        [
            ["Produkty sprawdzone", str(len(plan.products))],
            ["Elementy do oceny", str(len(plan.problem_items))],
            ["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.status_summary:
        print("\nStatusy produktow")
        print_table(["Status", "Liczba"], [[row["status"], str(row["count"])] for row in plan.status_summary])
    if plan.issue_summary:
        print("\nProblemy produktow")
        print_table(["Kod problemu", "Liczba"], [[row["issue_code"], str(row["count"])] for row in plan.issue_summary])
    if plan.problem_items:
        print("\nElementy do oceny")
        print_table(
            ["Nr", "Waznosc", "Produkt", "Status", "Wysw. 30d", "Klik. 30d", "Flagi"],
            [
                [
                    str(index),
                    item["severity"],
                    item["item_id"],
                    item["status"],
                    str(item["impressions_30d"]),
                    str(item["clicks_30d"]),
                    ", ".join(item["flags"]),
                ]
                for index, item in enumerate(plan.problem_items[:30], 1)
            ],
        )
        if len(plan.problem_items) > 30:
            print(f"... oraz {len(plan.problem_items) - 30} kolejnych elementow 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_shopping_product_status_plan(
    client_config: ClientConfig,
    plan: ShoppingProductStatusPlan,
    show_navigation: bool = True,
) -> None:
    print("\nTo zadanie jest audytem statusow produktow i nie wdraza zmian w Google Ads, adsPRO ani Merchant Center.")
    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",
            "product": ", ".join(item["item_id"] for item in plan.problem_items[:10]),
            "summary": {
                "products": len(plan.products),
                "problem_items": len(plan.problem_items),
                "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_shopping_product_statuses(
    client_config: ClientConfig,
    global_rules: dict,
    plan_only: bool = False,
    apply_plan_path: str | None = None,
    confirm_apply: str | None = None,
    show_navigation: bool = True,
) -> None:
    if apply_plan_path:
        if confirm_apply != "TAK":
            print("Do oznaczenia audytu jako wykonanego wymagane jest --confirm-apply TAK.")
            if show_navigation:
                print_next_navigation(client_config.domain)
            return
        plan_data = json.loads(Path(apply_plan_path).read_text(encoding="utf-8"))
        if plan_data.get("client") != client_config.domain:
            print(f"Plan jest dla klienta {plan_data.get('client')}, a wybrano {client_config.domain}.")
            if show_navigation:
                print_next_navigation(client_config.domain)
            return
        plan = ShoppingProductStatusPlan.from_dict(plan_data)
        print_shopping_product_status_plan(plan)
        apply_shopping_product_status_plan(client_config, plan, show_navigation=show_navigation)
        return

    print(f"\nKlient: {client_config.domain}")
    print("Przygotowuje plan sprawdzenia statusow produktow Shopping...")
    plan = build_shopping_product_status_plan(client_config, global_rules)
    print_shopping_product_status_plan(plan)
    json_path, md_path = save_shopping_product_status_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",
            "product": ", ".join(item["item_id"] for item in plan.problem_items[:10]),
            "summary": {
                "products": len(plan.products),
                "problem_items": len(plan.problem_items),
                "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 statusow produktow Shopping.")
    if show_navigation:
        print_next_navigation(client_config.domain)
