from __future__ import annotations

import argparse
import json
import os
import subprocess
import sys
from pathlib import Path
from collections import Counter
from datetime import datetime
from textwrap import wrap

from .cleanup import cleanup_old_plans, plan_retention_days, print_cleanup_result
from .config import load_config, load_env
from .knowledge.importer import import_knowledge_file
from .knowledge.legacy_import import import_legacy_lancedb
from .knowledge.store import (
    approve_task_suggestion,
    assign_rule_to_task,
    delete_rule,
    ensure_knowledge_store,
    list_rules,
    load_rules,
    mark_review_progress,
    pending_task_suggestions,
    reject_task_suggestion,
    reset_review_state,
    rule_preview,
    rules_for_task,
    review_start_index,
    search_rules,
    store_summary,
    unassigned_rules,
    update_rule_status,
)
from .knowledge.vector_index import build_vector_index, semantic_search, vector_index_summary
from .reminders import add_reminder, print_client_reminders
from .table import print_table
from .task_catalog import (
    load_groups,
    load_tasks,
    print_compact_task_list,
    print_task_list,
    task_by_number,
    task_by_selection,
    tasks_by_group_number,
    tasks_by_selection_group,
)
from .tasks.pla_settings_check import run_check_pla_settings
from .tasks.pla_cl1_sync import run_sync_pla_cl1
from .tasks.ai_account_audit import run_ai_account_audit
from .tasks.product_cl1_bestseller_split import run_split_pla_cl1_bestsellers
from .tasks.shopping_troas_ag_optimization import run_optimize_shopping_troas_ag
from .tasks.account_anomaly_check import run_check_account_anomalies
from .tasks.ad_asset_status_check import run_check_ad_asset_statuses
from .tasks.ad_schedule_check import run_check_ad_schedules
from .tasks.bidding_strategy_check import run_check_bidding_strategies
from .tasks.budget_usage_check import run_check_budget_usage
from .tasks.campaign_language_check import run_check_campaign_languages
from .tasks.campaign_location_check import run_check_campaign_locations
from .tasks.campaign_network_check import run_check_campaign_networks
from .tasks.conversion_tracking_check import run_check_conversion_tracking
from .tasks.device_performance_check import run_check_device_performance
from .tasks.feed_merchant_quality_check import run_check_feed_merchant_quality
from .tasks.auction_insights_check import run_check_auction_insights
from .tasks.impression_share_check import run_check_impression_share
from .tasks.keyword_status_check import run_check_keyword_statuses
from .tasks.pmax_structure_check import run_check_pmax_structure
from .tasks.remarketing_setup_check import run_check_remarketing_setup
from .tasks.rsa_assets_check import run_check_rsa_assets
from .tasks.search_basic_settings_check import run_check_search_basic_settings
from .tasks.search_terms_check import run_check_search_terms
from .tasks.shopping_product_status_check import run_check_shopping_product_statuses
from .tasks.product_feed_optimization import (
    run_fill_product_unit_pricing,
    run_optimize_product_categories,
    run_optimize_product_feed,
    run_optimize_product_titles,
)
from .tasks.product_availability_check import run_check_product_availability
from .tasks.additional_audits import (
    run_check_ad_group_performance,
    run_check_age_performance,
    run_check_conversion_action_performance,
    run_check_day_of_week_performance,
    run_check_gender_performance,
    run_check_hour_of_day_performance,
    run_check_keyword_quality_score,
    run_check_landing_page_performance,
    run_check_network_performance,
    run_check_shopping_product_performance,
)


def choose_index(label: str, options: list[str]) -> int | None:
    print(f"\n{label}")
    for i, option in enumerate(options, 1):
        print(f"{i}. {option}")
    try:
        raw = input("Wybierz numer albo Enter aby wyjsc: ").strip()
    except EOFError:
        print()
        return None
    if not raw:
        return None
    try:
        idx = int(raw)
    except ValueError:
        print("Nieprawidlowy numer.")
        return None
    if idx < 1 or idx > len(options):
        print("Nieprawidlowy numer.")
        return None
    return idx - 1


def _task_id_set(value) -> set[str]:
    if value is None:
        return set()
    if isinstance(value, str):
        values = [value]
    elif isinstance(value, (list, tuple, set)):
        values = value
    else:
        return set()
    return {str(item).strip() for item in values if str(item).strip()}


def all_selection_skip_task_ids(client) -> set[str]:
    return _task_id_set(client.skip_in_all)


def tasks_for_all_selection(tasks, client):
    skipped_ids = all_selection_skip_task_ids(client)
    if not skipped_ids:
        return list(tasks), []
    selected = []
    skipped = []
    for task in tasks:
        if task.id in skipped_ids:
            skipped.append(task)
        else:
            selected.append(task)
    return selected, skipped


def print_all_selection_skips(client, skipped_tasks) -> None:
    if not skipped_tasks:
        return
    print()
    print("Pominieto w wyborze ALL zgodnie z config/clients.toml:")
    print_table(
        ["Nr", "Task ID", "Zadanie"],
        [[task.selection, task.id, task.name] for task in skipped_tasks],
    )


def main() -> None:
    if hasattr(sys.stdout, "reconfigure"):
        sys.stdout.reconfigure(encoding="utf-8", errors="replace")
    parser = argparse.ArgumentParser(description="Google Ads ver 2")
    parser.add_argument(
        "command",
        nargs="?",
        choices=["analiza-klienta", "analiza-zadania", "wiedza", "notatka-przypomnij", "raport-klienta"],
        help="Tryb klient -> zadanie, zadanie -> wszyscy klienci, baza wiedzy albo przypomnienie",
    )
    parser.add_argument(
        "knowledge_action",
        nargs="?",
        help="Komenda wiedzy: init, dodaj, propozycje, zatwierdz, odrzuc, szukaj, szukaj-ai, reguly, lista, statystyki",
    )
    parser.add_argument(
        "knowledge_query",
        nargs="*",
        help="Zapytanie dla komendy wiedza szukaj",
    )
    parser.add_argument("--client", help="Domena klienta z config/clients.toml")
    parser.add_argument("--client-number", type=int, help="Numer klienta z listy analiza-klienta")
    parser.add_argument("--month", help="Miesiac raportu: YYYY-MM albo MM.YYYY")
    parser.add_argument("--client-name", help="Czytelna nazwa klienta na raporcie")
    parser.add_argument("--confirm-upload", help="Wymagane do uploadu raportu. Uzyj dokladnie: TAK")
    parser.add_argument("--confirm-recommendations", help="Wymagane do wygenerowania HTML z proponowanymi wnioskami. Uzyj dokladnie: TAK")
    parser.add_argument("--skip-ga4", action="store_true", help="Pomin GA4 przy raporcie klienta")
    parser.add_argument(
        "--task",
        choices=[
            "sync_pla_cl1",
            "split_pla_cl1_bestsellers",
            "optimize_shopping_troas_ag",
            "check_pla_settings",
            "optimize_product_feed",
            "optimize_product_titles",
            "optimize_product_categories",
            "fill_product_unit_pricing",
            "check_product_availability",
            "check_conversion_tracking",
            "check_search_basic_settings",
            "check_budget_usage",
            "check_bidding_strategies",
            "check_search_terms",
            "check_rsa_assets",
            "check_feed_merchant_quality",
            "check_pmax_structure",
            "check_remarketing_setup",
            "check_account_anomalies",
            "check_campaign_locations",
            "check_campaign_networks",
            "check_campaign_languages",
            "check_ad_schedules",
            "check_ad_asset_statuses",
            "check_keyword_statuses",
            "check_shopping_product_statuses",
            "check_impression_share",
            "check_auction_insights",
            "check_device_performance",
            "check_day_of_week_performance",
            "check_hour_of_day_performance",
            "check_network_performance",
            "check_ad_group_performance",
            "check_keyword_quality_score",
            "check_landing_page_performance",
            "check_conversion_action_performance",
            "check_shopping_product_performance",
            "check_gender_performance",
            "check_age_performance",
            "ai_account_audit",
        ],
        help="Zadanie do uruchomienia bez menu",
    )
    parser.add_argument("--select", help="Wybór z listy zadan, np. 1.1, 1.0 albo ALL")
    parser.add_argument("--task-number", type=int, help="Numer zadania z listy analiza-klienta")
    parser.add_argument("--group-number", type=int, help="Uruchom wszystkie zadania z grupy o podanym numerze")
    parser.add_argument("--group-all-current", action="store_true", help="Uruchom wszystkie zadania z pierwszej widocznej grupy")
    parser.add_argument("--all-groups", action="store_true", help="Uruchom wszystkie zadania ze wszystkich grup")
    parser.add_argument("--file", help="Plik zrodlowy dla komendy wiedza dodaj")
    parser.add_argument("--source", help="Czytelna nazwa zrodla dla komendy wiedza dodaj")
    parser.add_argument("--model", help="Model OpenAI dla komendy wiedza dodaj")
    parser.add_argument("--rule-id", help="ID reguly dla akceptacji lub odrzucenia propozycji zadania")
    parser.add_argument("--topic", help="Filtr tematu dla komend wiedzy")
    parser.add_argument("--status", help="Filtr statusu regul: active, draft, archived, duplicate")
    parser.add_argument("--duplicate-of", help="ID reguly nadrzednej dla komendy wiedza duplikat")
    parser.add_argument("--from", dest="from_path", help="Sciezka zrodlowa, np. stara baza LanceDB")
    parser.add_argument("--table", default="fakty", help="Nazwa tabeli przy imporcie starej LanceDB")
    parser.add_argument("--import-limit", type=int, help="Opcjonalny limit importowanych rekordow ze starej bazy")
    parser.add_argument("--restart", action="store_true", help="Zacznij przeglad nieprzypisanych regul od poczatku")
    parser.add_argument("--max-rules", type=int, default=12, help="Maksymalna liczba regul z jednego importu wiedzy")
    parser.add_argument("--dry-run", action="store_true", help="Sprawdz import wiedzy bez wywolania API i zapisu regul")
    parser.add_argument("--limit", type=int, help="Limit wynikow dla komend wiedzy")
    parser.add_argument("--plan-only", action="store_true", help="Tylko przygotuj plan i zapisz go do pliku")
    parser.add_argument("--apply-plan", help="Wdroz zapisany plan JSON")
    parser.add_argument("--keep-plans-days", type=int, help="Ile dni trzymac stare plany przed autoczyszczeniem")
    parser.add_argument(
        "--confirm-apply",
        help="Wymagane przy --apply-plan. Uzyj dokladnie: TAK",
    )
    args = parser.parse_args()

    load_env()
    if args.command == "wiedza":
        handle_knowledge_command(args)
        return

    try:
        cfg = load_config()
    except Exception as exc:
        print(exc)
        sys.exit(1)

    domains = sorted(cfg.clients)
    if not domains:
        print("Brak klientow w config/clients.toml.")
        return

    cleanup_days = args.keep_plans_days if args.keep_plans_days is not None else plan_retention_days(cfg.global_rules)
    if args.command in {"analiza-klienta", "analiza-zadania"}:
        print_cleanup_result(cleanup_old_plans(cleanup_days))

    tasks = load_tasks()
    groups = load_groups()
    selected_domain = args.client
    if args.client_number:
        if args.client_number < 1 or args.client_number > len(domains):
            print(f"Nie ma klienta numer {args.client_number}.")
            return
        selected_domain = domains[args.client_number - 1]

    if args.command == "notatka-przypomnij":
        reminder_text = " ".join([part for part in [args.knowledge_action, *args.knowledge_query] if part]).strip()
        if not reminder_text:
            print("Podaj tresc przypomnienia, np.:")
            print('python gads.py notatka-przypomnij --client aruba.rzeszow.pl "Za 4 tygodnie przypomnij mi o porownaniu PLA wzgledem PMax"')
            return
        if selected_domain and selected_domain not in cfg.clients:
            print(f"Nie znaleziono klienta {selected_domain} w config/clients.toml.")
            return
        try:
            reminder = add_reminder(reminder_text, domain=selected_domain)
        except Exception as exc:
            print(f"Nie udalo sie zapisac przypomnienia: {exc}")
            return
        print("\nDodano przypomnienie.")
        print_table(
            ["Pole", "Wartosc"],
            [
                ["Termin", reminder.due_date],
                ["Zakres", reminder.client or "globalne"],
                ["Notatka", reminder.text],
            ],
        )
        return

    if args.command == "raport-klienta":
        handle_client_report_command(args, cfg, domains, selected_domain)
        return

    if (
        args.command == "analiza-klienta"
        and resolve_report_domain(args.knowledge_action, domains) in cfg.clients
        and args.knowledge_query
        and looks_like_report_month(args.knowledge_query[0])
    ):
        handle_client_report_command(args, cfg, domains, selected_domain)
        return

    if args.command == "analiza-klienta":
        if not selected_domain:
            print("\nWybierz klienta:")
            print_table(["Nr", "Domena"], [[str(i), domain] for i, domain in enumerate(domains, 1)])
            print("\nNastepny krok:")
            print("python gads.py analiza-klienta --client-number <numer>")
            return

        if selected_domain not in cfg.clients:
            print(f"Nie znaleziono klienta {selected_domain} w config/clients.toml.")
            return

        if not args.select and not args.task_number and not args.group_number and not args.group_all_current and not args.all_groups:
            print(f"\nKlient: {selected_domain}")
            print_client_reminders(selected_domain)
            print_task_list(tasks)
            print("\nNastepny krok:")
            print(
                "python gads.py analiza-klienta "
                f"--client-number {domains.index(selected_domain) + 1} "
                "--select <nr> --plan-only"
            )
            return

        if args.select:
            selected = args.select.strip()
            if selected.upper() == "ALL":
                selected_tasks, skipped_tasks = tasks_for_all_selection(
                    tasks,
                    cfg.clients[selected_domain],
                )
                print_all_selection_skips(cfg.clients[selected_domain], skipped_tasks)
                if not selected_tasks:
                    print("Brak zadan do uruchomienia po zastosowaniu pominiec dla ALL.")
                    return
                run_task_sequence(
                    selected_tasks,
                    cfg.clients[selected_domain],
                    cfg.global_rules,
                    plan_only=args.plan_only,
                )
                return
            selected_group_tasks = tasks_by_selection_group(tasks, groups, selected)
            if selected_group_tasks:
                run_task_sequence(
                    selected_group_tasks,
                    cfg.clients[selected_domain],
                    cfg.global_rules,
                    plan_only=args.plan_only,
                )
                return
            selected_task = task_by_selection(tasks, selected)
            if not selected_task:
                print(f"Nie ma wyboru {selected}.")
                return
            run_task(
                selected_task.id,
                cfg.clients[selected_domain],
                cfg.global_rules,
                plan_only=args.plan_only,
                apply_plan_path=args.apply_plan,
                confirm_apply=args.confirm_apply,
            )
            return

        if args.group_all_current:
            first_group_number = groups[0].number if groups else None
            selected_tasks = tasks_by_group_number(tasks, groups, first_group_number) if first_group_number else []
            if not selected_tasks:
                print("Brak zadan w pierwszej grupie.")
                return
            run_task_sequence(
                selected_tasks,
                cfg.clients[selected_domain],
                cfg.global_rules,
                plan_only=args.plan_only,
            )
            return

        if args.group_number:
            selected_tasks = tasks_by_group_number(tasks, groups, args.group_number)
            if not selected_tasks:
                print(f"Nie ma grupy numer {args.group_number}.")
                return
            run_task_sequence(
                selected_tasks,
                cfg.clients[selected_domain],
                cfg.global_rules,
                plan_only=args.plan_only,
            )
            return

        if args.all_groups:
            selected_tasks, skipped_tasks = tasks_for_all_selection(
                tasks,
                cfg.clients[selected_domain],
            )
            print_all_selection_skips(cfg.clients[selected_domain], skipped_tasks)
            if not selected_tasks:
                print("Brak zadan do uruchomienia po zastosowaniu pominiec dla ALL.")
                return
            run_task_sequence(
                selected_tasks,
                cfg.clients[selected_domain],
                cfg.global_rules,
                plan_only=args.plan_only,
            )
            return

        selected_task = task_by_number(tasks, args.task_number)
        if not selected_task:
            print(f"Nie ma zadania numer {args.task_number}.")
            return
        run_task(
            selected_task.id,
            cfg.clients[selected_domain],
            cfg.global_rules,
            plan_only=args.plan_only,
            apply_plan_path=args.apply_plan,
            confirm_apply=args.confirm_apply,
        )
        return

    if args.command != "analiza-zadania" and (args.client or args.task):
        if not args.client or not args.task:
            print("Dla trybu bez menu podaj jednoczesnie --client i --task.")
            return
        if args.client not in cfg.clients:
            print(f"Nie znaleziono klienta {args.client} w config/clients.toml.")
            return
        run_task(
            args.task,
            cfg.clients[args.client],
            cfg.global_rules,
            plan_only=args.plan_only,
            apply_plan_path=args.apply_plan,
            confirm_apply=args.confirm_apply,
        )
        return

    if args.command == "analiza-zadania":
        if args.apply_plan:
            print("Tryb analiza-zadania przygotowuje plany dla wielu klientow i nie obsluguje --apply-plan.")
            print("Wdrozenie wykonuj osobno dla konkretnego klienta i konkretnego pliku planu.")
            return

        if not args.select and not args.task and not args.task_number and not args.group_number and not args.all_groups:
            print("\nWybierz zadanie do uruchomienia dla wszystkich klientow:")
            print_compact_task_list(tasks)
            print("\nNastepny krok:")
            print("python gads.py analiza-zadania --select <nr>")
            print("\nTa komenda przygotuje plany po kolei dla wszystkich klientow z config/clients.toml.")
            return

        selected_tasks = []
        respect_all_skips = False
        if args.select:
            selected = args.select.strip()
            if selected.upper() == "ALL":
                selected_tasks = tasks
                respect_all_skips = True
            else:
                selected_tasks = tasks_by_selection_group(tasks, groups, selected)
                if not selected_tasks:
                    selected_task = task_by_selection(tasks, selected)
                    selected_tasks = [selected_task] if selected_task else []
            if not selected_tasks:
                print(f"Nie ma wyboru {selected}.")
                return
        elif args.group_number:
            selected_tasks = tasks_by_group_number(tasks, groups, args.group_number)
            if not selected_tasks:
                print(f"Nie ma grupy numer {args.group_number}.")
                return
        elif args.all_groups:
            selected_tasks = tasks
            respect_all_skips = True
        elif args.task:
            selected_tasks = [task for task in tasks if task.id == args.task]
            if not selected_tasks:
                print(f"Nie ma zadania {args.task}.")
                return
        elif args.task_number:
            selected_task = task_by_number(tasks, args.task_number)
            selected_tasks = [selected_task] if selected_task else []
            if not selected_tasks:
                print(f"Nie ma zadania numer {args.task_number}.")
                return

        run_tasks_for_all_clients(
            selected_tasks,
            cfg,
            domains,
            plan_only=True,
            pause_after_client=True,
            respect_all_skips=respect_all_skips,
        )
        return

    client_idx = choose_index("Klient", domains)
    if client_idx is None:
        return
    client = cfg.clients[domains[client_idx]]

    task_labels = [task.name for task in tasks]
    task_idx = choose_index("Zadanie", task_labels)
    if task_idx is None:
        return

    run_task(tasks[task_idx].id, client, cfg.global_rules)


def handle_knowledge_command(args: argparse.Namespace) -> None:
    if not args.knowledge_action:
        print("\nKomendy bazy wiedzy:")
        print("python gads.py wiedza init")
        print('python gads.py wiedza dodaj --file "knowledge/sources/artykul.md" --source "Nazwa zrodla"')
        print("python gads.py wiedza propozycje")
        print("python gads.py wiedza zatwierdz --rule-id <id_reguly> --task <task_id>")
        print("python gads.py wiedza odrzuc --rule-id <id_reguly> --task <task_id>")
        print('python gads.py wiedza szukaj "pmax shopping"')
        print("python gads.py wiedza reguly --task check_pla_settings")
        print("python gads.py wiedza lista --topic shopping")
        print("python gads.py wiedza statystyki")
        print("python gads.py wiedza indeksuj")
        print('python gads.py wiedza szukaj-ai "czy PMax kanibalizuje Display?"')
        print('python gads.py wiedza import-stare --from "D:\\google ads\\lancedb"')
        print("python gads.py wiedza przypisz")
        return

    if args.knowledge_action == "init":
        paths = ensure_knowledge_store()
        summary = store_summary()
        print("\nLokalna baza wiedzy jest gotowa.")
        print_table(
            ["Element", "Sciezka"],
            [
                ["Katalog wiedzy", str(paths["knowledge_dir"])],
                ["Materialy zrodlowe", str(paths["sources_dir"])],
                ["Reguly JSONL", str(paths["rules_path"])],
                ["Historia importow", str(paths["imports_path"])],
                ["LanceDB", str(paths["lancedb_dir"])],
            ],
        )
        print_table(
            ["Metryka", "Liczba"],
            [
                ["Reguly", str(summary["rules"])],
                ["Reguly aktywne", str(summary["active_rules"])],
                ["Reguly robocze", str(summary["draft_rules"])],
                ["Reguly zarchiwizowane", str(summary["archived_rules"])],
                ["Reguly-duplikaty", str(summary["duplicate_rules"])],
                ["Tematy", str(summary["topics"])],
                ["Zadania zatwierdzone", str(summary["task_ids"])],
                ["Zadania proponowane", str(summary["suggested_task_ids"])],
                ["Propozycje do akceptacji", str(summary["suggestions"])],
                ["Zrodla", str(summary["sources"])],
            ],
        )
        index_meta = vector_index_summary()
        if index_meta:
            print("\nIndeks semantyczny")
            print_table(
                ["Pole", "Wartosc"],
                [
                    ["Model", str(index_meta.get("model", ""))],
                    ["Reguly w indeksie", str(index_meta.get("rules_count", ""))],
                    ["Utworzono", str(index_meta.get("created_at", ""))],
                    ["Tabela zapisana w meta", str(index_meta.get("table_path", ""))],
                    ["Tabela dla tego komputera", str(index_meta.get("current_table_path", ""))],
                    ["Dostepny lokalnie", "TAK" if index_meta.get("current_available") else "NIE"],
                ],
            )
            if not index_meta.get("current_available"):
                print("Uruchom `python gads.py wiedza indeksuj`, aby odbudowac indeks na tym komputerze.")
        return

    if args.knowledge_action == "dodaj":
        if not args.file:
            print('Podaj plik, np. python gads.py wiedza dodaj --file "knowledge/sources/artykul.md" --source "Nazwa zrodla"')
            return
        if not args.source:
            print('Podaj --source, np. --source "Optmyzr blog"')
            return
        try:
            result = import_knowledge_file(
                file_path=Path(args.file),
                source_name=args.source,
                model=args.model,
                max_rules=args.max_rules,
                dry_run=args.dry_run,
            )
        except Exception as exc:
            print(f"Nie udalo sie dodac wiedzy: {exc}")
            return
        if result.dry_run:
            print("\nImport wiedzy sprawdzony w trybie dry-run.")
            print("API nie zostalo wywolane i reguly nie zostaly zapisane.")
        else:
            print("\nDodano wiedze do lokalnej bazy.")
        print_table(
            ["Pole", "Wartosc"],
            [
                ["Plik", str(result.source_path)],
                ["Zrodlo", result.source_name],
                ["Model", result.model],
                ["Nowe reguly", str(result.rules_count)],
                ["Uwagi", result.notes],
            ],
        )
        if not result.dry_run:
            ask_about_task_suggestions(rule_ids=set(result.rule_ids), limit=knowledge_limit(args))
        return

    if args.knowledge_action == "import-stare":
        source_path = Path(args.from_path) if args.from_path else Path(r"D:\google ads\lancedb")
        try:
            result = import_legacy_lancedb(
                db_path=source_path,
                table_name=args.table,
                limit=args.import_limit,
            )
        except Exception as exc:
            print(f"Nie udalo sie zaimportowac starej LanceDB: {exc}")
            return
        print("\nZaimportowano wiedze ze starej LanceDB bez przypisywania do zadan.")
        print_table(
            ["Pole", "Wartosc"],
            [
                ["Baza", str(result.db_path)],
                ["Tabela", result.table_name],
                ["Rekordy w zrodle", str(result.total_rows)],
                ["Dodano regul", str(result.imported_count)],
                ["Pominieto istniejace", str(result.skipped_existing_count)],
            ],
        )
        print("\nPo imporcie odswiez indeks semantyczny:")
        print("python gads.py wiedza indeksuj")
        return

    if args.knowledge_action == "propozycje":
        ask_about_task_suggestions(limit=knowledge_limit(args))
        return

    if args.knowledge_action == "przypisz":
        review_unassigned_rules(limit=knowledge_limit(args, default=1), restart=args.restart)
        return

    if args.knowledge_action in {"zatwierdz", "odrzuc"}:
        if not args.rule_id:
            print("Podaj --rule-id.")
            return
        if not args.task:
            print("Podaj --task z istniejacym task_id.")
            return
        try:
            if args.knowledge_action == "zatwierdz":
                rule = approve_task_suggestion(args.rule_id, args.task)
                print(f"Zatwierdzono przypisanie reguly {rule.id} do zadania {args.task}.")
            else:
                rule = reject_task_suggestion(args.rule_id, args.task)
                print(f"Odrzucono propozycje przypisania reguly {rule.id} do zadania {args.task}.")
        except Exception as exc:
            print(f"Nie udalo sie zaktualizowac propozycji: {exc}")
            return
        return

    if args.knowledge_action == "szukaj":
        query = " ".join(args.knowledge_query).strip()
        if not query:
            print('Podaj zapytanie, np. python gads.py wiedza szukaj "pmax shopping"')
            return
        results = search_rules(query, limit=knowledge_limit(args))
        if not results:
            print("Brak pasujacych regul.")
            return
        rows = [
            [
                str(index),
                str(result.score),
                result.rule.id,
                result.rule.topic,
                ", ".join(result.rule.task_ids),
                rule_preview(result.rule),
            ]
            for index, result in enumerate(results, 1)
        ]
        print_table(["Nr", "Wynik", "ID", "Temat", "Zadania", "Regula"], rows)
        return

    if args.knowledge_action == "szukaj-ai":
        query = " ".join(args.knowledge_query).strip()
        if not query:
            print('Podaj zapytanie, np. python gads.py wiedza szukaj-ai "czy PMax kanibalizuje Display?"')
            return
        try:
            results = semantic_search(query, limit=knowledge_limit(args), model=args.model)
        except Exception as exc:
            print(f"Nie udalo sie wykonac wyszukiwania semantycznego: {exc}")
            return
        if not results:
            print("Brak pasujacych regul.")
            return
        rows = [
            [
                str(index),
                f"{result.distance:.4f}",
                result.id,
                result.topic,
                result.task_ids,
                result.text,
            ]
            for index, result in enumerate(results, 1)
        ]
        print_table(["Nr", "Dystans", "ID", "Temat", "Zadania", "Regula"], rows)
        return

    if args.knowledge_action == "reguly":
        if not args.task:
            print("Podaj zadanie przez --task, np. python gads.py wiedza reguly --task check_pla_settings")
            return
        rules = rules_for_task(args.task)
        if not rules:
            print(f"Brak regul dla zadania: {args.task}")
            return
        rows = [
            [str(index), rule.id, rule.topic, rule.rule_type, rule_preview(rule)]
            for index, rule in enumerate(rules[: max(knowledge_limit(args), 0)], 1)
        ]
        print_table(["Nr", "ID", "Temat", "Typ", "Regula"], rows)
        limit = knowledge_limit(args)
        if len(rules) > limit:
            print(f"... oraz {len(rules) - limit} kolejnych regul")
        return

    if args.knowledge_action == "indeksuj":
        try:
            result = build_vector_index(model=args.model)
        except Exception as exc:
            print(f"Nie udalo sie zbudowac indeksu LanceDB: {exc}")
            return
        print("\nZbudowano indeks semantyczny LanceDB.")
        print_table(
            ["Pole", "Wartosc"],
            [
                ["Tabela", str(result.table_path)],
                ["Model", result.model],
                ["Reguly", str(result.rules_count)],
            ],
        )
        return

    if args.knowledge_action == "lista":
        rules = list_rules(
            topic=args.topic,
            task_id=args.task,
            status=args.status,
            source=args.source,
        )
        if not rules:
            print("Brak regul dla podanych filtrow.")
            return
        shown = rules[: max(knowledge_limit(args), 0)]
        rows = [
            [
                str(index),
                rule.id,
                rule.status,
                rule.topic,
                ", ".join(rule.task_ids),
                rule.source,
                rule_preview(rule),
            ]
            for index, rule in enumerate(shown, 1)
        ]
        print_table(["Nr", "ID", "Status", "Temat", "Zadania", "Zrodlo", "Regula"], rows)
        if len(rules) > len(shown):
            print(f"... oraz {len(rules) - len(shown)} kolejnych regul")
        return

    if args.knowledge_action == "statystyki":
        print_knowledge_statistics()
        return

    if args.knowledge_action in {"archiwizuj", "aktywuj", "duplikat"}:
        if not args.rule_id:
            print("Podaj --rule-id.")
            return
        try:
            if args.knowledge_action == "archiwizuj":
                rule = update_rule_status(args.rule_id, "archived")
                print(f"Zarchiwizowano regule: {rule.id}")
            elif args.knowledge_action == "aktywuj":
                rule = update_rule_status(args.rule_id, "active")
                print(f"Aktywowano regule: {rule.id}")
            else:
                if not args.duplicate_of:
                    print("Podaj --duplicate-of z ID reguly nadrzednej.")
                    return
                rule = update_rule_status(args.rule_id, "duplicate", duplicate_of=args.duplicate_of)
                print(f"Oznaczono regule {rule.id} jako duplikat: {args.duplicate_of}")
        except Exception as exc:
            print(f"Nie udalo sie zmienic statusu reguly: {exc}")
            return
        return

    print(f"Nieznana komenda wiedzy: {args.knowledge_action}")


def knowledge_limit(args: argparse.Namespace, default: int = 10) -> int:
    if args.limit is None:
        return default
    return args.limit


def print_knowledge_statistics() -> None:
    rules = load_rules()
    if not rules:
        print("Baza wiedzy nie ma jeszcze regul.")
        return

    status_counts = Counter(rule.status for rule in rules)
    topic_counts = Counter(rule.topic for rule in rules)
    source_counts = Counter(rule.source for rule in rules)
    task_counts = Counter(task_id for rule in rules for task_id in rule.task_ids)
    pending_count = sum(
        1
        for rule in rules
        for task_id in rule.suggested_task_ids
        if task_id not in rule.task_ids
    )

    print("\nStatusy")
    print_table(
        ["Status", "Liczba"],
        [[status, str(count)] for status, count in sorted(status_counts.items())],
    )
    print(f"\nPropozycje do akceptacji: {pending_count}")

    print("\nTematy")
    print_table(
        ["Temat", "Liczba"],
        [[topic, str(count)] for topic, count in topic_counts.most_common()],
    )

    print("\nZadania")
    if task_counts:
        print_table(
            ["Zadanie", "Liczba regul"],
            [[task_id, str(count)] for task_id, count in task_counts.most_common()],
        )
    else:
        print("Brak zatwierdzonych przypisan do zadan.")

    print("\nZrodla")
    print_table(
        ["Zrodlo", "Liczba"],
        [[source, str(count)] for source, count in source_counts.most_common()],
    )


def review_unassigned_rules(limit: int = 10, restart: bool = False) -> None:
    if restart:
        reset_review_state()

    rules = unassigned_rules()
    if not rules:
        print("Brak aktywnych regul bez przypisania do zadan.")
        return

    start = 0 if restart else review_start_index(rules)
    if start >= len(rules):
        print("Przeglad doszedl do konca kolejki nieprzypisanych regul.")
        print("Aby przejrzec je od poczatku, uruchom:")
        print("python gads.py wiedza przypisz --restart")
        return

    tasks = load_tasks()
    task_rows = [
        [str(index), task.id, task.group_name, task.name]
        for index, task in enumerate(tasks, 1)
    ]
    task_by_index = {str(index): task.id for index, task in enumerate(tasks, 1)}
    task_ids = {task.id for task in tasks}
    batch = rules[start : start + max(limit, 0)]

    print(f"\nNieprzypisane reguly: {len(rules)}. Start: {start + 1}/{len(rules)}.")
    print("Mozesz przerwac wpisujac Q. Wznowienie zacznie od tej samej reguly.")

    for offset, rule in enumerate(batch, start + 1):
        print("\n" + "=" * 72)
        print(f"Regula {offset}/{len(rules)}")
        print("=" * 72)
        print_rule_review(rule)
        print("\nDostepne zadania")
        print_table(["Nr", "Task ID", "Grupa", "Zadanie"], task_rows)
        print("\nWpisz numer zadania, kilka numerow po przecinku, task_id, P aby pominac, U aby usunac, Q aby przerwac.")
        try:
            answer = input(f"Do jakiego zadania dodac regule {rule.id}? ").strip()
        except EOFError:
            print("\nBrak wejscia terminala. Postep zostaje przed aktualna regula.")
            return

        sort_key = [rule.topic, rule.id]
        queue_index = offset - 1
        if not answer:
            print("Zostawiono bez zmian. Postep zapisany.")
            mark_review_progress(rule.id, sort_key=sort_key, queue_index=queue_index)
            continue
        if answer.upper() == "Q":
            print("Przerwano. Wznowienie zacznie od tej reguly.")
            return
        if answer.upper() in {"P", "POMIN", "SKIP"}:
            print("Pominieto. Postep zapisany.")
            mark_review_progress(rule.id, sort_key=sort_key, queue_index=queue_index)
            continue
        if answer.upper() in {"U", "USUN", "DELETE", "D"}:
            try:
                confirmation = input(
                    f"Aby trwale usunac regule {rule.id} z rules.jsonl, wpisz USUN: "
                ).strip()
            except EOFError:
                print("\nBrak potwierdzenia. Regula zostaje bez zmian.")
                return
            if confirmation != "USUN":
                print("Nie usunieto reguly. Wznowienie zacznie od tej reguly.")
                return
            try:
                delete_rule(rule.id)
            except Exception as exc:
                print(f"Nie udalo sie usunac reguly: {exc}")
                return
            print("Usunieto regule z rules.jsonl. Postep zapisany.")
            print("Po zakonczeniu przegladu odswiez indeks: python gads.py wiedza indeksuj")
            mark_review_progress(rule.id, sort_key=sort_key, queue_index=queue_index)
            continue

        selected_task_ids = parse_task_selection(answer, task_by_index, task_ids)
        if not selected_task_ids:
            print("Nie rozpoznano zadania. Regula zostaje bez zmian, postep zapisany.")
            mark_review_progress(rule.id, sort_key=sort_key, queue_index=queue_index)
            continue

        for task_id in selected_task_ids:
            try:
                assign_rule_to_task(rule.id, task_id)
            except Exception as exc:
                print(f"Nie udalo sie przypisac do {task_id}: {exc}")
                continue
            print(f"Dodano do zadania: {task_id}")
        mark_review_progress(rule.id, sort_key=sort_key, queue_index=queue_index)

    print("\nKoniec tej porcji przegladu.")
    print("Aby kontynuowac:")
    print("python gads.py wiedza przypisz")


def parse_task_selection(answer: str, task_by_index: dict[str, str], task_ids: set[str]) -> list[str]:
    selected: list[str] = []
    for raw_part in answer.replace(";", ",").split(","):
        part = raw_part.strip()
        if not part:
            continue
        task_id = task_by_index.get(part) or part
        if task_id not in task_ids:
            print(f"Nie ma zadania: {part}")
            continue
        if task_id not in selected:
            selected.append(task_id)
    return selected


def print_rule_review(rule) -> None:
    rows = [
        ["ID", rule.id],
        ["Status", rule.status],
        ["Temat", rule.topic],
        ["Typ", rule.rule_type],
        ["Zrodlo", rule.source],
        ["Plik zrodlowy", rule.source_file],
        ["Pewnosc", rule.confidence],
    ]
    if rule.suggested_task_ids:
        rows.append(["Proponowane zadania", ", ".join(rule.suggested_task_ids)])
    print_table(["Pole", "Wartosc"], rows)

    print_wrapped_block("Warunek", rule.condition)
    print_wrapped_block("Rekomendacja", rule.recommendation or rule.text)
    print_wrapped_block("Ryzyko", rule.risk)
    if rule.text and rule.text not in {rule.condition, rule.recommendation, rule.risk}:
        print_wrapped_block("Tekst do wyszukiwania", rule.text)


def print_wrapped_block(title: str, value: str, width: int = 110) -> None:
    print(f"\n{title}:")
    raw_value = (value or "").strip()
    if not raw_value:
        print("  (brak)")
        return

    for paragraph in raw_value.splitlines():
        clean_paragraph = " ".join(paragraph.split())
        if not clean_paragraph:
            print()
            continue
        wrapped_lines = wrap(
            clean_paragraph,
            width=width,
            break_long_words=False,
            break_on_hyphens=False,
        )
        for line in wrapped_lines or [""]:
            print(f"  {line}")


def preview_text(value: str, max_length: int = 220) -> str:
    value = " ".join((value or "").split())
    if len(value) <= max_length:
        return value
    return value[: max_length - 3].rstrip() + "..."


def ask_about_task_suggestions(rule_ids: set[str] | None = None, limit: int = 10) -> None:
    suggestions = pending_task_suggestions()
    if rule_ids is not None:
        suggestions = [item for item in suggestions if item["rule"].id in rule_ids]
    if not suggestions:
        print("\nBrak propozycji przypisania regul do zadan.")
        return

    shown = suggestions[: max(limit, 0)]
    print("\nPropozycje przypisania regul do zadan")
    rows = []
    for index, item in enumerate(shown, 1):
        rule = item["rule"]
        rows.append(
            [
                str(index),
                rule.id,
                rule.topic,
                item["task_id"],
                rule_preview(rule),
            ]
        )
    print_table(["Nr", "ID reguly", "Temat", "Proponowane zadanie", "Regula"], rows)
    if len(suggestions) > len(shown):
        print(f"... oraz {len(suggestions) - len(shown)} kolejnych propozycji")

    print("\nDla kazdej propozycji wpisz:")
    print("- TAK: dopisz zadanie do reguly")
    print("- NIE: odrzuc propozycje")
    print("- Enter: zostaw propozycje do pozniejszej decyzji")

    for item in shown:
        rule = item["rule"]
        task_id = item["task_id"]
        try:
            answer = input(f"Dodac regule {rule.id} do zadania {task_id}? ").strip()
        except EOFError:
            print("\nBrak wejscia terminala. Propozycje zostaja oczekujace.")
            return
        if not answer:
            print("Zostawiono jako oczekujace.")
            continue
        if answer == "TAK":
            try:
                approve_task_suggestion(rule.id, task_id)
            except Exception as exc:
                print(f"Nie udalo sie zatwierdzic: {exc}")
                continue
            print("Zatwierdzono.")
            continue
        if answer.upper() in {"NIE", "NO"}:
            try:
                reject_task_suggestion(rule.id, task_id)
            except Exception as exc:
                print(f"Nie udalo sie odrzucic: {exc}")
                continue
            print("Odrzucono.")
            continue
        print("Nie rozpoznano odpowiedzi. Propozycja zostaje oczekujaca.")


def normalize_report_month(raw: str | None) -> str | None:
    if not raw:
        return None
    value = raw.strip()
    if not value:
        return None
    if "." in value:
        month, year = value.split(".", 1)
        return f"{int(year):04d}-{int(month):02d}"
    if "-" in value:
        left, right = value.split("-", 1)
        if len(left) == 2 and len(right) == 4:
            month, year = left, right
        else:
            year, month = left, right
        return f"{int(year):04d}-{int(month):02d}"
    raise ValueError("Miesiac podaj jako YYYY-MM, MM-YYYY albo MM.YYYY.")


def looks_like_report_month(raw: str | None) -> bool:
    try:
        normalize_report_month(raw)
    except Exception:
        return False
    return True


def report_slug(domain: str) -> str:
    return domain.replace(".pl", "").replace(".", "-")


def default_client_report_name(domain: str) -> str:
    return domain.replace(".pl", "").replace("-", " ").replace(".", " ").title()


def resolve_report_domain(raw: str | None, domains: list[str]) -> str | None:
    if not raw:
        return None
    value = raw.strip()
    if value in domains:
        return value
    matches = [
        domain
        for domain in domains
        if domain.startswith(f"{value}.") or domain.replace(".pl", "") == value
    ]
    if len(matches) == 1:
        return matches[0]
    return value


def run_checked(command: list[str], cwd: Path) -> None:
    print()
    print("Uruchamiam:")
    print(" ".join(command))
    completed = subprocess.run(command, cwd=str(cwd), text=True)
    if completed.returncode != 0:
        raise RuntimeError(f"Komenda zakonczyla sie kodem {completed.returncode}.")


def handle_client_report_command(args: argparse.Namespace, cfg, domains: list[str], selected_domain: str | None) -> None:
    root = Path(__file__).resolve().parents[2]
    positional_domain = args.knowledge_action
    positional_month = args.knowledge_query[0] if args.knowledge_query else None
    selected_domain = resolve_report_domain(selected_domain, domains)
    resolved_positional_domain = resolve_report_domain(positional_domain, domains)
    if not selected_domain and resolved_positional_domain in cfg.clients:
        selected_domain = resolved_positional_domain
    month_raw = args.month or positional_month
    if selected_domain and not month_raw and positional_domain and resolved_positional_domain not in cfg.clients:
        month_raw = positional_domain

    if not selected_domain:
        print("\nWybierz klienta:")
        print_table(["Nr", "Domena"], [[str(i), domain] for i, domain in enumerate(domains, 1)])
        print("\nNastepny krok:")
        print("python gads.py raport-klienta --client-number <numer> --month <YYYY-MM>")
        return
    if selected_domain not in cfg.clients:
        print(f"Nie znaleziono klienta {selected_domain} w config/clients.toml.")
        return
    try:
        month = normalize_report_month(month_raw)
    except ValueError as exc:
        print(str(exc))
        return
    if not month:
        print("Podaj miesiac raportu, np.:")
        print(f"python gads.py raport-klienta --client {selected_domain} --month {datetime.now().strftime('%Y-%m')}")
        return

    client_name = args.client_name or default_client_report_name(selected_domain)
    data_path = root / "scripts" / "reports" / "output" / f"{selected_domain}_{month}.json"
    html_path = root / "scripts" / "reports" / "output" / selected_domain / month / "index.html"
    recommendations_path = root / "scripts" / "reports" / "output" / f"{selected_domain}_{month}_recommendations.json"

    fetch_cmd = [
        sys.executable,
        "scripts/reports/fetch_monthly_report_data.py",
        "--customer",
        selected_domain,
        "--month",
        month,
    ]
    if args.skip_ga4:
        fetch_cmd.append("--skip-ga4")

    generate_cmd = [
        sys.executable,
        "scripts/reports/generate_html_report.py",
        "--data",
        str(data_path),
        "--client",
        client_name,
    ]

    print(f"\nRaport klienta: {selected_domain}")
    print(f"Miesiac: {month}")
    print(f"Nazwa w raporcie: {client_name}")

    try:
        run_checked(fetch_cmd, root)
        if data_path.exists():
            recommendations = ensure_report_recommendations_file(data_path, recommendations_path)
            print_report_recommendations(recommendations, recommendations_path)
            if args.confirm_recommendations != "TAK":
                print("\nHTML nie zostal jeszcze wygenerowany.")
                print("Agent AI powinien przygotowac albo poprawic wnioski w pliku rekomendacji.")
                print("Po akceptacji tresci uruchom:")
                print(
                    "python gads.py raport-klienta "
                    f"--client {selected_domain} --month {month} --client-name \"{client_name}\" --confirm-recommendations TAK"
                )
                print("\nSkrypt nie generuje wnioskow samodzielnie. Plik rekomendacji uzupelnia agent AI przed generowaniem HTML.")
                return
            apply_report_recommendations(data_path, recommendations_path)
        run_checked(generate_cmd, root)
    except Exception as exc:
        print(f"\nNie udalo sie wygenerowac raportu: {exc}")
        return

    print("\nRaport HTML jest gotowy do sprawdzenia:")
    print(html_path)
    print("\nUpload nie zostal wykonany bez potwierdzenia.")
    print("Aby wyslac raport na serwer:")
    print(
        "python gads.py raport-klienta "
        f"--client {selected_domain} --month {month} --client-name \"{client_name}\" --confirm-upload TAK"
    )

    if args.confirm_upload != "TAK":
        return

    missing = [key for key in ("ADSPRO_HOST", "ADSPRO_USERNAME", "ADSPRO_PASSWORD", "ADSPRO_REMOTE_PATH") if not os.environ.get(key)]
    if missing:
        print(f"\nBrak danych FTP w .env: {', '.join(missing)}")
        return

    remote_path = f"/raporty/{report_slug(selected_domain)}/{month}/"
    upload_cmd = [
        sys.executable,
        "scripts/reports/upload_report_ftp.py",
        "--local-dir",
        str(html_path.parent),
        "--remote-path",
        remote_path,
    ]
    try:
        run_checked(upload_cmd, root)
    except Exception as exc:
        print(f"\nNie udalo sie wyslac raportu: {exc}")
        return

    print("\nRaport wyslany.")
    print(f"URL: https://adspro.projectpro.pl{remote_path}")


def build_report_recommendation_context(data_path: Path) -> dict:
    data = json.loads(data_path.read_text(encoding="utf-8"))
    ads = data.get("google_ads", {})
    totals = ads.get("totals", {})
    prev_totals = ads.get("prev_totals", {})
    mom = ads.get("mom_change", {})
    campaigns = sorted(ads.get("campaigns", []), key=lambda row: row.get("cost", 0), reverse=True)
    ga4 = data.get("ga4") or {}
    ecom = ga4.get("ecommerce") or {}
    ecom_current = ecom.get("current") or {}
    ecom_mom = ecom.get("mom_change") or {}
    return {
        "google_ads_totals": {
            "cost": totals.get("cost", 0),
            "clicks": totals.get("clicks", 0),
            "conversions": totals.get("conversions", 0),
            "conversion_value": totals.get("conversion_value", 0),
            "roas": totals.get("roas", 0),
            "cpa": totals.get("cpa", 0),
        },
        "google_ads_mom_change": {
            "cost_pct": mom.get("cost_pct", 0),
            "clicks_pct": mom.get("clicks_pct", 0),
            "conversions_pct": mom.get("conversions_pct", 0),
            "conversion_value_pct": mom.get("conversion_value_pct", 0),
            "roas_pct": mom.get("roas_pct", 0),
            "cpa_pct": mom.get("cpa_pct", 0),
        },
        "ga4_ecommerce": {
            "transactions": ecom_current.get("transactions", 0),
            "revenue": ecom_current.get("revenue", 0),
            "transactions_pct": ecom_mom.get("transactions_pct", 0),
            "revenue_pct": ecom_mom.get("revenue_pct", 0),
        },
        "top_campaigns_by_cost": [
            {
                "name": campaign.get("name", ""),
                "cost": campaign.get("cost", 0),
                "conversions": campaign.get("conversions", 0),
                "conversion_value": campaign.get("conversion_value", 0),
                "roas": campaign.get("roas", 0),
            }
            for campaign in campaigns[:5]
        ],
    }


def ensure_report_recommendations_file(data_path: Path, path: Path) -> list[dict]:
    path.parent.mkdir(parents=True, exist_ok=True)
    if path.exists():
        content = json.loads(path.read_text(encoding="utf-8"))
        if isinstance(content, dict):
            return content.get("recommendations", [])
        return content
    payload = {
        "source": "agent_ai",
        "instruction": (
            "Uzupelnia agent AI po analizie danych raportu. Skrypt nie powinien sam generowac "
            "wnioskow ani rekomendacji."
        ),
        "context": build_report_recommendation_context(data_path),
        "recommendations": [
            {
                "icon": "&#10148;",
                "title": "DO UZUPELNIENIA PRZEZ AGENTA AI",
                "text": "Przygotuj konkretny wniosek i rekomendacje na podstawie danych raportu.",
            }
        ],
    }
    path.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")
    return payload["recommendations"]


def print_report_recommendations(recommendations: list[dict], path: Path) -> None:
    print("\nPropozycje wnioskow i rekomendacji do raportu")
    print_table(
        ["Nr", "Tytul", "Tekst"],
        [
            [str(index), item.get("title", ""), item.get("text", "")]
            for index, item in enumerate(recommendations, 1)
        ],
    )
    print(f"\nPlik rekomendacji: {path}")


def apply_report_recommendations(data_path: Path, recommendations_path: Path) -> None:
    data = json.loads(data_path.read_text(encoding="utf-8"))
    content = json.loads(recommendations_path.read_text(encoding="utf-8"))
    recommendations = content.get("recommendations", []) if isinstance(content, dict) else content
    unfinished = [
        item
        for item in recommendations
        if "DO UZUPELNIENIA" in item.get("title", "") or "DO UZUPELNIENIA" in item.get("text", "")
    ]
    if unfinished:
        raise RuntimeError("Plik rekomendacji nadal zawiera pozycje do uzupelnienia przez agenta AI.")
    data["recommendations"] = recommendations
    data_path.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8")


def run_task(
    task_id,
    client,
    global_rules,
    plan_only: bool = False,
    apply_plan_path: str | None = None,
    confirm_apply: str | None = None,
    show_navigation: bool = True,
) -> None:
    if task_id == "ai_account_audit":
        run_ai_account_audit(
            client,
            global_rules,
            plan_only=plan_only,
            apply_plan_path=apply_plan_path,
            confirm_apply=confirm_apply,
            show_navigation=show_navigation,
        )
        return
    if task_id == "sync_pla_cl1":
        run_sync_pla_cl1(
            client,
            global_rules,
            plan_only=plan_only,
            apply_plan_path=apply_plan_path,
            confirm_apply=confirm_apply,
            show_navigation=show_navigation,
        )
        return
    if task_id == "split_pla_cl1_bestsellers":
        run_split_pla_cl1_bestsellers(
            client,
            global_rules,
            plan_only=plan_only,
            apply_plan_path=apply_plan_path,
            confirm_apply=confirm_apply,
            show_navigation=show_navigation,
        )
        return
    if task_id == "optimize_shopping_troas_ag":
        run_optimize_shopping_troas_ag(
            client,
            global_rules,
            plan_only=plan_only,
            apply_plan_path=apply_plan_path,
            confirm_apply=confirm_apply,
            show_navigation=show_navigation,
        )
        return
    if task_id == "check_pla_settings":
        run_check_pla_settings(
            client,
            global_rules,
            plan_only=plan_only,
            apply_plan_path=apply_plan_path,
            confirm_apply=confirm_apply,
            show_navigation=show_navigation,
        )
        return
    if task_id == "optimize_product_feed":
        run_optimize_product_feed(
            client,
            global_rules,
            plan_only=plan_only,
            apply_plan_path=apply_plan_path,
            confirm_apply=confirm_apply,
            show_navigation=show_navigation,
        )
        return
    if task_id == "optimize_product_titles":
        run_optimize_product_titles(
            client,
            global_rules,
            plan_only=plan_only,
            apply_plan_path=apply_plan_path,
            confirm_apply=confirm_apply,
            show_navigation=show_navigation,
        )
        return
    if task_id == "optimize_product_categories":
        run_optimize_product_categories(
            client,
            global_rules,
            plan_only=plan_only,
            apply_plan_path=apply_plan_path,
            confirm_apply=confirm_apply,
            show_navigation=show_navigation,
        )
        return
    if task_id == "fill_product_unit_pricing":
        run_fill_product_unit_pricing(
            client,
            global_rules,
            plan_only=plan_only,
            apply_plan_path=apply_plan_path,
            confirm_apply=confirm_apply,
            show_navigation=show_navigation,
        )
        return
    if task_id == "check_product_availability":
        run_check_product_availability(
            client,
            global_rules,
            plan_only=plan_only,
            apply_plan_path=apply_plan_path,
            confirm_apply=confirm_apply,
            show_navigation=show_navigation,
        )
        return
    if task_id == "check_conversion_tracking":
        run_check_conversion_tracking(
            client,
            global_rules,
            plan_only=plan_only,
            apply_plan_path=apply_plan_path,
            confirm_apply=confirm_apply,
            show_navigation=show_navigation,
        )
        return
    if task_id == "check_search_basic_settings":
        run_check_search_basic_settings(
            client,
            global_rules,
            plan_only=plan_only,
            apply_plan_path=apply_plan_path,
            confirm_apply=confirm_apply,
            show_navigation=show_navigation,
        )
        return
    if task_id == "check_budget_usage":
        run_check_budget_usage(
            client,
            global_rules,
            plan_only=plan_only,
            apply_plan_path=apply_plan_path,
            confirm_apply=confirm_apply,
            show_navigation=show_navigation,
        )
        return
    if task_id == "check_bidding_strategies":
        run_check_bidding_strategies(
            client,
            global_rules,
            plan_only=plan_only,
            apply_plan_path=apply_plan_path,
            confirm_apply=confirm_apply,
            show_navigation=show_navigation,
        )
        return
    if task_id == "check_search_terms":
        run_check_search_terms(
            client,
            global_rules,
            plan_only=plan_only,
            apply_plan_path=apply_plan_path,
            confirm_apply=confirm_apply,
            show_navigation=show_navigation,
        )
        return
    if task_id == "check_rsa_assets":
        run_check_rsa_assets(
            client,
            global_rules,
            plan_only=plan_only,
            apply_plan_path=apply_plan_path,
            confirm_apply=confirm_apply,
            show_navigation=show_navigation,
        )
        return
    if task_id == "check_feed_merchant_quality":
        run_check_feed_merchant_quality(
            client,
            global_rules,
            plan_only=plan_only,
            apply_plan_path=apply_plan_path,
            confirm_apply=confirm_apply,
            show_navigation=show_navigation,
        )
        return
    if task_id == "check_pmax_structure":
        run_check_pmax_structure(
            client,
            global_rules,
            plan_only=plan_only,
            apply_plan_path=apply_plan_path,
            confirm_apply=confirm_apply,
            show_navigation=show_navigation,
        )
        return
    if task_id == "check_remarketing_setup":
        run_check_remarketing_setup(
            client,
            global_rules,
            plan_only=plan_only,
            apply_plan_path=apply_plan_path,
            confirm_apply=confirm_apply,
            show_navigation=show_navigation,
        )
        return
    if task_id == "check_account_anomalies":
        run_check_account_anomalies(
            client,
            global_rules,
            plan_only=plan_only,
            apply_plan_path=apply_plan_path,
            confirm_apply=confirm_apply,
            show_navigation=show_navigation,
        )
        return
    if task_id == "check_campaign_locations":
        run_check_campaign_locations(
            client,
            global_rules,
            plan_only=plan_only,
            apply_plan_path=apply_plan_path,
            confirm_apply=confirm_apply,
            show_navigation=show_navigation,
        )
        return
    if task_id == "check_campaign_networks":
        run_check_campaign_networks(
            client,
            global_rules,
            plan_only=plan_only,
            apply_plan_path=apply_plan_path,
            confirm_apply=confirm_apply,
            show_navigation=show_navigation,
        )
        return
    if task_id == "check_campaign_languages":
        run_check_campaign_languages(
            client,
            global_rules,
            plan_only=plan_only,
            apply_plan_path=apply_plan_path,
            confirm_apply=confirm_apply,
            show_navigation=show_navigation,
        )
        return
    if task_id == "check_ad_schedules":
        run_check_ad_schedules(
            client,
            global_rules,
            plan_only=plan_only,
            apply_plan_path=apply_plan_path,
            confirm_apply=confirm_apply,
            show_navigation=show_navigation,
        )
        return
    if task_id == "check_ad_asset_statuses":
        run_check_ad_asset_statuses(
            client,
            global_rules,
            plan_only=plan_only,
            apply_plan_path=apply_plan_path,
            confirm_apply=confirm_apply,
            show_navigation=show_navigation,
        )
        return
    if task_id == "check_keyword_statuses":
        run_check_keyword_statuses(
            client,
            global_rules,
            plan_only=plan_only,
            apply_plan_path=apply_plan_path,
            confirm_apply=confirm_apply,
            show_navigation=show_navigation,
        )
        return
    if task_id == "check_shopping_product_statuses":
        run_check_shopping_product_statuses(
            client,
            global_rules,
            plan_only=plan_only,
            apply_plan_path=apply_plan_path,
            confirm_apply=confirm_apply,
            show_navigation=show_navigation,
        )
        return
    if task_id == "check_impression_share":
        run_check_impression_share(
            client,
            global_rules,
            plan_only=plan_only,
            apply_plan_path=apply_plan_path,
            confirm_apply=confirm_apply,
            show_navigation=show_navigation,
        )
        return
    if task_id == "check_auction_insights":
        run_check_auction_insights(
            client,
            global_rules,
            plan_only=plan_only,
            apply_plan_path=apply_plan_path,
            confirm_apply=confirm_apply,
            show_navigation=show_navigation,
        )
        return
    if task_id == "check_device_performance":
        run_check_device_performance(
            client,
            global_rules,
            plan_only=plan_only,
            apply_plan_path=apply_plan_path,
            confirm_apply=confirm_apply,
            show_navigation=show_navigation,
        )
        return
    if task_id == "check_day_of_week_performance":
        run_check_day_of_week_performance(
            client,
            global_rules,
            plan_only=plan_only,
            apply_plan_path=apply_plan_path,
            confirm_apply=confirm_apply,
            show_navigation=show_navigation,
        )
        return
    if task_id == "check_hour_of_day_performance":
        run_check_hour_of_day_performance(
            client,
            global_rules,
            plan_only=plan_only,
            apply_plan_path=apply_plan_path,
            confirm_apply=confirm_apply,
            show_navigation=show_navigation,
        )
        return
    if task_id == "check_network_performance":
        run_check_network_performance(
            client,
            global_rules,
            plan_only=plan_only,
            apply_plan_path=apply_plan_path,
            confirm_apply=confirm_apply,
            show_navigation=show_navigation,
        )
        return
    if task_id == "check_ad_group_performance":
        run_check_ad_group_performance(
            client,
            global_rules,
            plan_only=plan_only,
            apply_plan_path=apply_plan_path,
            confirm_apply=confirm_apply,
            show_navigation=show_navigation,
        )
        return
    if task_id == "check_keyword_quality_score":
        run_check_keyword_quality_score(
            client,
            global_rules,
            plan_only=plan_only,
            apply_plan_path=apply_plan_path,
            confirm_apply=confirm_apply,
            show_navigation=show_navigation,
        )
        return
    if task_id == "check_landing_page_performance":
        run_check_landing_page_performance(
            client,
            global_rules,
            plan_only=plan_only,
            apply_plan_path=apply_plan_path,
            confirm_apply=confirm_apply,
            show_navigation=show_navigation,
        )
        return
    if task_id == "check_conversion_action_performance":
        run_check_conversion_action_performance(
            client,
            global_rules,
            plan_only=plan_only,
            apply_plan_path=apply_plan_path,
            confirm_apply=confirm_apply,
            show_navigation=show_navigation,
        )
        return
    if task_id == "check_shopping_product_performance":
        run_check_shopping_product_performance(
            client,
            global_rules,
            plan_only=plan_only,
            apply_plan_path=apply_plan_path,
            confirm_apply=confirm_apply,
            show_navigation=show_navigation,
        )
        return
    if task_id == "check_gender_performance":
        run_check_gender_performance(
            client,
            global_rules,
            plan_only=plan_only,
            apply_plan_path=apply_plan_path,
            confirm_apply=confirm_apply,
            show_navigation=show_navigation,
        )
        return
    if task_id == "check_age_performance":
        run_check_age_performance(
            client,
            global_rules,
            plan_only=plan_only,
            apply_plan_path=apply_plan_path,
            confirm_apply=confirm_apply,
            show_navigation=show_navigation,
        )
        return
    print(f"Zadanie {task_id} nie ma jeszcze implementacji.")


def run_task_sequence(tasks, client, global_rules, plan_only: bool = False) -> None:
    total = len(tasks)
    if plan_only:
        print("Tryb zbiorczy plan-only przygotuje plany po kolei.")
        print("Agent powinien analizowac i wdrazac kazdy plan osobno, przed przejsciem do kolejnego zadania.")
    for index, task in enumerate(tasks, 1):
        print()
        print("#" * 72)
        print(f"Zadanie {index}/{total}: {task.group_name} / {task.name}")
        print("#" * 72)
        run_task(task.id, client, global_rules, plan_only=plan_only, show_navigation=False)
    print()
    print("Zakonczono sekwencje zadan.")
    print_sequence_navigation(client.domain)


def prompt_after_client(domain: str, has_next: bool) -> bool:
    if not has_next:
        return True
    print()
    print("Co dalej po tym kliencie?")
    print("Enter -> nastepny klient")
    print("q -> zakoncz tryb analiza-zadania")
    try:
        answer = input("Wybor: ").strip().lower()
    except EOFError:
        print()
        return True
    if answer in {"q", "quit", "koniec", "3"}:
        print(f"Przerwano po kliencie {domain}.")
        return False
    return True


def run_tasks_for_all_clients(
    tasks,
    cfg,
    domains: list[str],
    plan_only: bool = True,
    pause_after_client: bool = True,
    respect_all_skips: bool = False,
) -> None:
    total_clients = len(domains)
    total_tasks = len(tasks)
    print("\nTryb analiza-zadania")
    print("Zadania beda uruchamiane po kolei dla wszystkich klientow.")
    print("Dla bezpieczenstwa ten tryb przygotowuje tylko plany i nie wdraza zmian.")
    print_table(
        ["Metryka", "Liczba"],
        [
            ["Klienci", str(total_clients)],
            ["Zadania", str(total_tasks)],
            ["Tryb", "plan-only"],
            ["Pauza po kliencie", "TAK" if pause_after_client else "NIE"],
        ],
    )
    for client_index, domain in enumerate(domains, 1):
        print()
        print("#" * 72)
        print(f"Klient {client_index}/{total_clients}: {domain}")
        print("#" * 72)
        client_tasks = list(tasks)
        skipped_tasks = []
        if respect_all_skips:
            client_tasks, skipped_tasks = tasks_for_all_selection(
                tasks,
                cfg.clients[domain],
            )
            print_all_selection_skips(cfg.clients[domain], skipped_tasks)
        total_client_tasks = len(client_tasks)
        if not client_tasks:
            print("Brak zadan do uruchomienia po zastosowaniu pominiec dla ALL.")
            continue
        for task_index, task in enumerate(client_tasks, 1):
            print()
            print("-" * 72)
            print(f"Zadanie {task_index}/{total_client_tasks}: {task.group_name} / {task.name}")
            print("-" * 72)
            try:
                run_task(
                    task.id,
                    cfg.clients[domain],
                    cfg.global_rules,
                    plan_only=plan_only,
                    show_navigation=False,
                )
            except Exception as exc:
                print(f"Nie udalo sie przygotowac planu {task.name} dla klienta {domain}: {exc}")
        if pause_after_client and not prompt_after_client(domain, client_index < total_clients):
            print()
            print("Zakonczono tryb analiza-zadania przed przejsciem do kolejnych klientow.")
            print("\nCo dalej:")
            print("1. Lista zadan")
            print("2. Lista klientow")
            print("3. Zakoncz")
            print("\nKomendy:")
            print("1 -> python gads.py analiza-zadania")
            print("2 -> python gads.py analiza-klienta")
            return
    print()
    print("Zakonczono tryb analiza-zadania.")
    print("\nCo dalej:")
    print("1. Lista zadan")
    print("2. Lista klientow")
    print("3. Zakoncz")
    print("\nKomendy:")
    print("1 -> python gads.py analiza-zadania")
    print("2 -> python gads.py analiza-klienta")


def print_sequence_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 print_next_navigation(client_number: int | None = None) -> None:
    print("\nCo dalej:")
    if client_number:
        print(f"1. Lista zadan klienta numer {client_number}")
        print("2. Lista klientow")
        print("3. Zakoncz")
        print("\nKomendy:")
        print(f"1 -> python gads.py analiza-klienta --client-number {client_number}")
        print("2 -> python gads.py analiza-klienta")
    else:
        print("1. Lista klientow")
        print("2. Zakoncz")
        print("\nKomendy:")
        print("1 -> python gads.py analiza-klienta")
