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_campaign_locations"
TASK_NAME = "Sprawdzenie lokalizacji kampanii"


SCOPE = [
    {
        "area": "Tryb kierowania",
        "check": "Pokaz pozytywny i negatywny tryb kierowania lokalizacja dla kazdej kampanii.",
    },
    {
        "area": "Lokalizacje docelowe",
        "check": "Wypisz lokalizacje dodane do kampanii i oznacz kampanie bez jawnych lokalizacji.",
    },
    {
        "area": "Wykluczone lokalizacje",
        "check": "Wypisz lokalizacje wykluczone i oznacz kampanie, w ktorych brakuje wykluczen do recznej oceny.",
    },
    {
        "area": "Sprawnosc miesieczna",
        "check": "Przygotuj szybki przeglad ustawien, ktory mozna wykonywac rzadziej niz budzety i anomalie.",
    },
]


OUT_OF_SCOPE = [
    "budzety i wykorzystanie budzetu",
    "strategie stawek oraz cele Docelowy ROAS/Docelowy CPA",
    "zapytania uzytkownikow i wykluczenia slow kluczowych",
    "reklamy, assety i kreacje",
    "wdrazanie zmian lokalizacji na koncie Google Ads",
]


@dataclass
class CampaignLocationPlan:
    campaigns: list[dict]
    geo_type_summary: list[dict]
    location_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": TASK_ID,
            "task_name": TASK_NAME,
            "campaigns": self.campaigns,
            "geo_type_summary": self.geo_type_summary,
            "location_summary": self.location_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) -> "CampaignLocationPlan":
        return cls(
            campaigns=data.get("campaigns", []),
            geo_type_summary=data.get("geo_type_summary", []),
            location_summary=data.get("location_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 human_geo(value: str) -> str:
    return {
        "PRESENCE": "Obecnosc",
        "PRESENCE_OR_INTEREST": "Obecnosc lub zainteresowanie",
        "SEARCH_INTEREST": "Zainteresowanie wyszukiwaniem",
        "UNKNOWN": "Nieznane",
        "UNSPECIFIED": "Nieokreslone",
    }.get(value, value)


def location_label(resource_name: str, geo_targets: dict[str, dict]) -> str:
    target = geo_targets.get(resource_name)
    if not target:
        return resource_name
    parts = [target.get("name", "")]
    country_code = target.get("country_code", "")
    target_type = target.get("target_type", "")
    suffix = ", ".join(part for part in [country_code, target_type] if part)
    if suffix:
        parts.append(f"({suffix})")
    return " ".join(part for part in parts if part).strip() or resource_name


def campaign_flags(campaign: dict) -> list[str]:
    flags = []
    if campaign["positive_geo_target_type"] != "PRESENCE":
        flags.append("kierowanie nie tylko na obecnosc")
    if campaign["positive_locations_count"] == 0:
        flags.append("brak jawnych lokalizacji")
    if campaign["negative_locations_count"] == 0:
        flags.append("brak wykluczen lokalizacji")
    if campaign["negative_geo_target_type"] in {"UNKNOWN", "UNSPECIFIED", ""}:
        flags.append("nieznany tryb wykluczen")
    return flags or ["ok"]


def fetch_campaign_geo_settings(client_config: ClientConfig) -> list[dict]:
    google_client = get_google_ads_client(use_proto_plus=True)
    rows = run_query(
        google_client,
        client_config.safe_customer_id,
        """
        SELECT
          campaign.id,
          campaign.name,
          campaign.status,
          campaign.advertising_channel_type,
          campaign.geo_target_type_setting.positive_geo_target_type,
          campaign.geo_target_type_setting.negative_geo_target_type
        FROM campaign
        WHERE campaign.status != 'REMOVED'
        """,
    )

    campaigns = []
    for row in rows:
        campaign = row.campaign
        positive_geo = enum_name(campaign.geo_target_type_setting.positive_geo_target_type)
        negative_geo = enum_name(campaign.geo_target_type_setting.negative_geo_target_type)
        campaigns.append(
            {
                "campaign_id": str(campaign.id),
                "campaign_name": campaign.name,
                "status": enum_name(campaign.status),
                "channel_type": enum_name(campaign.advertising_channel_type),
                "positive_geo_target_type": positive_geo,
                "positive_geo_target_type_label": human_geo(positive_geo),
                "negative_geo_target_type": negative_geo,
                "negative_geo_target_type_label": human_geo(negative_geo),
                "positive_locations": [],
                "negative_locations": [],
            }
        )
    return campaigns


def fetch_location_criteria(client_config: ClientConfig) -> tuple[dict[str, list[dict]], dict[str, list[dict]], dict[str, dict], list[str]]:
    google_client = get_google_ads_client(use_proto_plus=True)
    warnings = []
    rows = run_query(
        google_client,
        client_config.safe_customer_id,
        """
        SELECT
          campaign.id,
          campaign_criterion.criterion_id,
          campaign_criterion.negative,
          campaign_criterion.status,
          campaign_criterion.location.geo_target_constant
        FROM campaign_criterion
        WHERE campaign.status != 'REMOVED'
          AND campaign_criterion.status != 'REMOVED'
          AND campaign_criterion.type = 'LOCATION'
        """,
    )

    positive: dict[str, list[dict]] = {}
    negative: dict[str, list[dict]] = {}
    resource_names = set()
    for row in rows:
        campaign_id = str(row.campaign.id)
        criterion = row.campaign_criterion
        resource_name = str(criterion.location.geo_target_constant or "")
        if resource_name:
            resource_names.add(resource_name)
        item = {
            "criterion_id": str(criterion.criterion_id),
            "resource_name": resource_name,
            "status": enum_name(criterion.status),
            "negative": bool(criterion.negative),
        }
        target = negative if criterion.negative else positive
        target.setdefault(campaign_id, []).append(item)

    geo_targets = fetch_geo_target_names(google_client, client_config.safe_customer_id, sorted(resource_names), warnings)
    return positive, negative, geo_targets, warnings


def fetch_geo_target_names(google_client, customer_id: str, resource_names: list[str], warnings: list[str]) -> dict[str, dict]:
    if not resource_names:
        return {}
    geo_targets: dict[str, dict] = {}
    chunk_size = 200
    for start in range(0, len(resource_names), chunk_size):
        chunk = resource_names[start : start + chunk_size]
        quoted = ", ".join(f"'{name}'" for name in chunk)
        try:
            rows = run_query(
                google_client,
                customer_id,
                f"""
                SELECT
                  geo_target_constant.resource_name,
                  geo_target_constant.name,
                  geo_target_constant.country_code,
                  geo_target_constant.target_type,
                  geo_target_constant.status
                FROM geo_target_constant
                WHERE geo_target_constant.resource_name IN ({quoted})
                """,
            )
        except Exception as exc:
            warnings.append(f"Nie udalo sie pobrac nazw lokalizacji geo_target_constant: {exc}")
            return geo_targets
        for row in rows:
            target = row.geo_target_constant
            geo_targets[str(target.resource_name)] = {
                "resource_name": str(target.resource_name),
                "name": str(target.name or ""),
                "country_code": str(target.country_code or ""),
                "target_type": str(target.target_type or ""),
                "status": enum_name(target.status),
            }
    return geo_targets


def attach_locations(
    campaigns: list[dict],
    positive: dict[str, list[dict]],
    negative: dict[str, list[dict]],
    geo_targets: dict[str, dict],
) -> list[dict]:
    for campaign in campaigns:
        campaign_id = campaign["campaign_id"]
        campaign["positive_locations"] = [
            {
                **item,
                "label": location_label(item["resource_name"], geo_targets),
            }
            for item in positive.get(campaign_id, [])
        ]
        campaign["negative_locations"] = [
            {
                **item,
                "label": location_label(item["resource_name"], geo_targets),
            }
            for item in negative.get(campaign_id, [])
        ]
        campaign["positive_locations_count"] = len(campaign["positive_locations"])
        campaign["negative_locations_count"] = len(campaign["negative_locations"])
        campaign["flags"] = campaign_flags(campaign)
    campaigns.sort(key=lambda row: (row["channel_type"], row["campaign_name"]))
    return campaigns


def build_geo_type_summary(campaigns: list[dict]) -> list[dict]:
    counter = Counter(campaign["positive_geo_target_type_label"] for campaign in campaigns)
    return [{"positive_geo_target_type": key, "count": value} for key, value in counter.most_common()]


def build_location_summary(campaigns: list[dict]) -> list[dict]:
    return [
        {
            "metric": "Kampanie",
            "count": len(campaigns),
        },
        {
            "metric": "Kampanie bez jawnych lokalizacji",
            "count": sum(1 for campaign in campaigns if campaign["positive_locations_count"] == 0),
        },
        {
            "metric": "Kampanie bez wykluczen lokalizacji",
            "count": sum(1 for campaign in campaigns if campaign["negative_locations_count"] == 0),
        },
        {
            "metric": "Kampanie z kierowaniem innym niz Obecnosc",
            "count": sum(1 for campaign in campaigns if campaign["positive_geo_target_type"] != "PRESENCE"),
        },
    ]


def build_campaign_location_plan(client_config: ClientConfig) -> CampaignLocationPlan:
    warnings = []
    try:
        campaigns = fetch_campaign_geo_settings(client_config)
        positive, negative, geo_targets, location_warnings = fetch_location_criteria(client_config)
        warnings.extend(location_warnings)
        campaigns = attach_locations(campaigns, positive, negative, geo_targets)
    except Exception as exc:
        campaigns = []
        warnings.append(f"Nie udalo sie pobrac ustawien lokalizacji z Google Ads API: {exc}")

    if not campaigns:
        warnings.append("Nie znaleziono kampanii albo nie udalo sie pobrac ustawien lokalizacji.")

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

    return CampaignLocationPlan(
        campaigns=campaigns,
        geo_type_summary=build_geo_type_summary(campaigns),
        location_summary=build_location_summary(campaigns),
        scope=SCOPE,
        out_of_scope=OUT_OF_SCOPE,
        knowledge_rules=knowledge_rules,
        warnings=warnings,
    )


def join_location_labels(locations: list[dict], limit: int = 6) -> str:
    if not locations:
        return "(brak)"
    labels = [item.get("label") or item.get("resource_name") or "" for item in locations]
    shown = labels[:limit]
    if len(labels) > limit:
        shown.append(f"... +{len(labels) - limit}")
    return ", ".join(shown)


def save_campaign_location_plan(domain: str, plan: CampaignLocationPlan) -> 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 lokalizacji kampanii",
        "",
        f"Klient: {domain}",
        f"Utworzono: {ts.isoformat(timespec='seconds')}",
        "",
        "## Podsumowanie",
        "",
        f"- Kampanie: {len(plan.campaigns)}",
        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.location_summary:
        lines.extend(["## Podsumowanie lokalizacji", "", "| Metryka | Liczba |", "| --- | --- |"])
        for row in plan.location_summary:
            lines.append(f"| {row['metric']} | {row['count']} |")
        lines.append("")
    if plan.geo_type_summary:
        lines.extend(["## Tryby kierowania lokalizacja", "", "| Tryb | Liczba kampanii |", "| --- | --- |"])
        for row in plan.geo_type_summary:
            lines.append(f"| {row['positive_geo_target_type']} | {row['count']} |")
        lines.append("")
    if plan.campaigns:
        lines.extend(
            [
                "## Kampanie",
                "",
                "| Kampania | Typ | Status | Tryb lokalizacji | Tryb wykluczen | Lokalizacje | Wykluczenia | Flagi |",
                "| --- | --- | --- | --- | --- | --- | --- | --- |",
            ]
        )
        for campaign in plan.campaigns:
            lines.append(
                f"| {md_cell(campaign['campaign_name'])} | {campaign['channel_type']} | {campaign['status']} | "
                f"{campaign['positive_geo_target_type_label']} | {campaign['negative_geo_target_type_label']} | "
                f"{md_cell(join_location_labels(campaign['positive_locations']))} | "
                f"{md_cell(join_location_labels(campaign['negative_locations']))} | "
                f"{md_cell(', '.join(campaign['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_campaign_location_plan(plan: CampaignLocationPlan) -> None:
    print("\nPlan sprawdzenia lokalizacji kampanii")
    print_table(
        ["Metryka", "Liczba"],
        [
            ["Kampanie", str(len(plan.campaigns))],
            ["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.location_summary:
        print("\nPodsumowanie lokalizacji")
        print_table(["Metryka", "Liczba"], [[row["metric"], str(row["count"])] for row in plan.location_summary])
    if plan.geo_type_summary:
        print("\nTryby kierowania lokalizacja")
        print_table(
            ["Tryb", "Kampanie"],
            [[row["positive_geo_target_type"], str(row["count"])] for row in plan.geo_type_summary],
        )
    if plan.campaigns:
        print("\nKampanie")
        print_table(
            ["Nr", "Kampania", "Typ", "Tryb", "Lok.", "Wykl.", "Flagi"],
            [
                [
                    str(index),
                    campaign["campaign_name"],
                    campaign["channel_type"],
                    campaign["positive_geo_target_type_label"],
                    str(campaign["positive_locations_count"]),
                    str(campaign["negative_locations_count"]),
                    ", ".join(campaign["flags"]),
                ]
                for index, campaign in enumerate(plan.campaigns[:30], 1)
            ],
        )
        if len(plan.campaigns) > 30:
            print(f"... oraz {len(plan.campaigns) - 30} kolejnych kampanii 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_campaign_location_plan(
    client_config: ClientConfig,
    plan: CampaignLocationPlan,
    show_navigation: bool = True,
) -> None:
    print("\nTo zadanie jest audytem lokalizacji 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(campaign["campaign_name"] for campaign in plan.campaigns[:10]),
            "summary": {
                "campaigns": len(plan.campaigns),
                "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_campaign_locations(
    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 = CampaignLocationPlan.from_dict(plan_data)
        print_campaign_location_plan(plan)
        apply_campaign_location_plan(client_config, plan, show_navigation=show_navigation)
        return

    print(f"\nKlient: {client_config.domain}")
    print("Przygotowuje plan sprawdzenia lokalizacji kampanii...")
    plan = build_campaign_location_plan(client_config)
    print_campaign_location_plan(plan)
    json_path, md_path = save_campaign_location_plan(client_config.domain, plan)
    print(f"\nPlan JSON: {json_path}")
    print(f"Plan Markdown: {md_path}")

    append_history(
        client_config.domain,
        {
            "task": TASK_NAME,
            "status": "plan przygotowany",
            "campaign": ", ".join(campaign["campaign_name"] for campaign in plan.campaigns[:10]),
            "summary": {
                "campaigns": len(plan.campaigns),
                "knowledge_rules": len(plan.knowledge_rules),
                "changes": 0,
            },
        },
    )

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

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