"""Daily 19:05 cron job — finalize variant choice, build next batch, notify group chat."""
import asyncio
import csv
import json
import os
import re
import sqlite3
from datetime import datetime
from pathlib import Path
from typing import Dict, List, Set, Tuple

BASE_DIR   = Path(__file__).parent.parent
VARIANTS_FILE = BASE_DIR / "data/processed/aima_pending_variants.json"
STATE_FILE    = BASE_DIR / "data/processed/aima_bot_state.json"
DB            = BASE_DIR / "data/processed/aima_conversion_shadow.sqlite"
DATASET       = "lost_shops_202605191501"
BATCH_SIZE    = 30
CHAT_ID       = -5178178519
MARKETER      = "@anatolii_malinovskyi"

DEFAULT_ENV_FILES = [
    BASE_DIR / ".env",
    Path(r"C:\python_scripts\top_1_telegram_signals\.env"),
]

CONTACTS_FIELDS = [
    "pilot_id", "pilot_name", "lead_id", "dataset", "segment", "variant",
    "hypothesis", "gate1_text", "phone", "first_name", "last_name",
    "registered_at", "last_activity_at", "worker-do-not-send",
    "pre_contact_quality", "contamination_status", "notes",
]

LOG_FIELDS = [
    "pilot_id", "lead_id", "dataset", "hypothesis", "phone",
    "worker-do-not-send", "added_to_telegram", "telegram_contact_name",
    "gate1_message_sent", "replied", "reply_text", "next_gate", "notes",
    "sent_at_utc", "recipient_id", "message_len", "message_preview",
    "chat_started", "replied_at_utc", "reply_category",
    "pre_contact_quality", "contamination_status",
    "qualified_product_reply", "next_step_accepted", "negative_stop", "reply_class",
]


TG_SESSION        = BASE_DIR / "data/processed/telegram/aima_support_session"
SCREEN_BATCH_SIZE = 100  # contacts per ImportContactsRequest call


def normalize_phone(value):
    # type: (str) -> str
    return "".join(ch for ch in str(value or "") if ch.isdigit() or ch == "+")


async def screen_candidates_for_telegram(candidates, needed):
    # type: (list, int) -> list
    """Return first `needed` candidates that have a Telegram account.

    Imports candidates in batches via ImportContactsRequest and keeps those
    for which Telegram returns a User object.  Stops as soon as `needed`
    confirmed contacts are accumulated.
    """
    load_env()
    api_id   = os.environ.get("TG_API_ID")
    api_hash = os.environ.get("TG_API_HASH")
    if not api_id or not api_hash:
        raise RuntimeError("TG_API_ID/TG_API_HASH missing from .env")

    from telethon import TelegramClient
    from telethon.tl.functions.contacts import ImportContactsRequest
    from telethon.tl.types import InputPhoneContact

    client = TelegramClient(str(TG_SESSION), int(api_id), api_hash)
    await client.connect()
    if not await client.is_user_authorized():
        raise RuntimeError("Session not authorized — run aima_telegram_login.py first")

    found = []  # type: list
    total_scanned = 0

    for offset in range(0, len(candidates), SCREEN_BATCH_SIZE):
        if len(found) >= needed:
            break
        chunk = candidates[offset:offset + SCREEN_BATCH_SIZE]
        contacts_req = [
            InputPhoneContact(
                client_id=offset + i,
                phone=r[3],
                first_name=r[1] or r[3][-4:],
                last_name=r[2] or "",
            )
            for i, r in enumerate(chunk)
        ]
        result = await client(ImportContactsRequest(contacts_req))
        found_phones = {
            normalize_phone(getattr(u, "phone", "") or "").lstrip("+")
            for u in result.users
        }
        # retry_contacts: user exists but has privacy settings.
        # With Telegram Premium on sender account, get_entity may now resolve them.
        # Use asyncio.wait_for with 5s timeout to prevent hangs.
        retry_client_ids = {rc for rc in getattr(result, "retry_contacts", [])}
        if retry_client_ids:
            cid_map = {offset + i + 1: r for i, r in enumerate(chunk)}
            for cid in retry_client_ids:
                if cid in cid_map and len(found) < needed:
                    r = cid_map[cid]
                    try:
                        import asyncio as _aio
                        await _aio.wait_for(client.get_entity(r[3]), timeout=5.0)
                        found_phones.add(normalize_phone(r[3]).lstrip("+"))
                    except Exception:
                        pass  # still unreachable even with Premium
        for r in chunk:
            if normalize_phone(r[3]).lstrip("+") in found_phones:
                found.append(r)
                if len(found) >= needed:
                    break
        total_scanned += len(chunk)
        retry_n = len(getattr(result, "retry_contacts", []))
        print("[screen] scanned={} tg_in_batch={} retry={} total_found={}".format(
            total_scanned, len(result.users), retry_n, len(found)))

    await client.disconnect()
    print("[screen] done: {} з Telegram знайдено (перевірено {} кандидатів)".format(
        len(found), total_scanned))
    return found[:needed]


def load_env():
    for path in DEFAULT_ENV_FILES:
        if not path.exists():
            continue
        for raw in path.read_text(encoding="utf-8", errors="ignore").splitlines():
            line = raw.strip()
            if not line or line.startswith("#") or "=" not in line:
                continue
            k, v = line.split("=", 1)
            os.environ.setdefault(k.strip(), v.strip().strip('"').strip("'"))


def collect_used():
    # type: () -> Tuple[Set[str], Set[str]]
    used_leads = set()  # type: Set[str]
    used_phones = set()  # type: Set[str]
    proc = BASE_DIR / "data/processed"
    for p in sorted(proc.glob("*.csv")):
        with p.open(encoding="utf-8-sig") as f:
            for row in csv.DictReader(f):
                lid = str(row.get("lead_id", "") or "").strip()
                ph  = str(row.get("phone",   "") or "").strip()
                if lid:
                    used_leads.add(lid)
                if ph:
                    used_phones.add(ph)
    return used_leads, used_phones


def next_batch_key():
    # type: () -> str
    nums = []
    for p in (BASE_DIR / "data/processed").glob("aima_far_v*_contacts.csv"):
        m = re.search(r"v(\d+)_contacts", p.name)
        if m:
            nums.append(int(m.group(1)))
    return "v{}".format(max(nums) + 1) if nums else "v7"


def next_pilot_id_start():
    # type: () -> int
    max_id = 120
    for p in (BASE_DIR / "data/processed").glob("aima_far_v*_contacts.csv"):
        with p.open(encoding="utf-8-sig") as f:
            for row in csv.DictReader(f):
                try:
                    max_id = max(max_id, int(row.get("pilot_id", 0) or 0))
                except (ValueError, TypeError):
                    pass
    return max_id + 1


def build_next_batch(batch_key, variant_texts, pilot_id_start):
    # type: (str, Dict[str, str], int) -> Tuple[Path, Path]
    used_leads, used_phones = collect_used()

    conn = sqlite3.connect(str(DB))
    cur = conn.cursor()
    cur.execute(
        """SELECT lead_id, first_name, last_name, phone, registered_at, last_activity_at
           FROM aima_imported_contacts
           WHERE dataset = ?
           ORDER BY row_index DESC""",
        (DATASET,),
    )
    all_rows = cur.fetchall()
    conn.close()

    candidates = [
        r for r in all_rows
        if r[3] and str(r[0]) not in used_leads and r[3] not in used_phones
    ]
    if not candidates:
        raise RuntimeError("No unprocessed contacts available in dataset")

    # Pre-screen: scan candidates in batches, keep only those with Telegram accounts
    selected = asyncio.get_event_loop().run_until_complete(
        screen_candidates_for_telegram(candidates, BATCH_SIZE)
    )
    if not selected:
        raise RuntimeError(
            "No contacts with Telegram found (scanned {} candidates)".format(len(candidates))
        )
    if len(selected) < BATCH_SIZE:
        print("[warn] Partial batch: {}/{} contacts — proceeding anyway".format(
            len(selected), BATCH_SIZE))
    v_keys = list(variant_texts.keys())
    half = len(selected) // 2  # split evenly regardless of batch size
    pilot_name = "manual_far_{}".format(batch_key)

    contacts_csv = BASE_DIR / "data/processed/aima_far_{}_contacts.csv".format(batch_key)
    log_csv      = BASE_DIR / "data/processed/aima_far_{}_telegram_log.csv".format(batch_key)

    contact_rows = []
    log_rows = []

    for i, (lead_id, first_name, last_name, phone, registered_at, last_activity_at) in enumerate(selected):
        pilot_id = pilot_id_start + i
        variant  = v_keys[0] if i < half else v_keys[1 % len(v_keys)]
        gate1_text = variant_texts[variant]

        contact_rows.append({
            "pilot_id": pilot_id,
            "pilot_name": pilot_name,
            "lead_id": lead_id,
            "dataset": DATASET,
            "segment": "opened_no_store",
            "variant": variant,
            "hypothesis": variant,
            "gate1_text": gate1_text,
            "phone": phone,
            "first_name": first_name or "",
            "last_name": last_name or "",
            "registered_at": registered_at or "",
            "last_activity_at": last_activity_at or "",
            "worker-do-not-send": "",
            "pre_contact_quality": "unknown",
            "contamination_status": "unknown",
            "notes": "FAR candidate batch {}".format(batch_key),
        })

        log_row = {k: "" for k in LOG_FIELDS}
        log_row.update({
            "pilot_id": pilot_id,
            "lead_id": lead_id,
            "dataset": DATASET,
            "hypothesis": variant,
            "phone": phone,
            "worker-do-not-send": "",
            "pre_contact_quality": "unknown",
            "contamination_status": "unknown",
        })
        log_rows.append(log_row)

    with contacts_csv.open("w", encoding="utf-8", newline="") as f:
        writer = csv.DictWriter(f, fieldnames=CONTACTS_FIELDS)
        writer.writeheader()
        writer.writerows(contact_rows)

    with log_csv.open("w", encoding="utf-8", newline="") as f:
        writer = csv.DictWriter(f, fieldnames=LOG_FIELDS)
        writer.writeheader()
        writer.writerows(log_rows)

    return contacts_csv, log_csv


def write_send_script(batch_key):
    # type: (str) -> None
    """Generate send script for next batch by substituting v6 template."""
    src_path = BASE_DIR / "src/aima_batch_send_{}.py".format(batch_key)
    v6_path  = BASE_DIR / "src/aima_batch_send_v6.py"
    if not v6_path.exists():
        print("[warn] v6 send script not found, cannot generate {}".format(src_path.name))
        return
    src = v6_path.read_text(encoding="utf-8")
    src = src.replace("aima_far_v6_contacts.csv", "aima_far_{}_contacts.csv".format(batch_key))
    src = src.replace("aima_far_v6_telegram_log.csv", "aima_far_{}_telegram_log.csv".format(batch_key))
    src = src.replace("batch_send_v6", "batch_send_{}".format(batch_key))
    src = src.replace("AIMA FAR v6 pilot", "AIMA FAR {} pilot".format(batch_key))
    src_path.write_text(src, encoding="utf-8")
    print("[script] wrote {}".format(src_path.name))


def send_telegram(token, chat_id, text):
    import requests
    r = requests.post(
        "https://api.telegram.org/bot{}/sendMessage".format(token),
        json={"chat_id": chat_id, "text": text, "parse_mode": "HTML"},
        timeout=30,
    )
    return r.json()


def main():
    load_env()
    token = os.environ.get("AIMA_BOT_TOKEN")
    if not token:
        raise SystemExit("AIMA_BOT_TOKEN missing")

    if not VARIANTS_FILE.exists():
        print("[warn] {} not found — run aima_agent_prepare.py first".format(VARIANTS_FILE.name))
        return

    data = json.loads(VARIANTS_FILE.read_text(encoding="utf-8"))
    status   = data.get("status", "pending_choice")
    variants = data.get("variants", [])  # type: List[dict]
    chosen   = data.get("chosen", [])    # type: List[str]
    locked_label = data.get("locked_label") or "A"

    if not variants:
        print("[warn] no variants in file")
        return

    available_labels = [v["label"] for v in variants]

    # Resolve chosen labels. A is a locked champion/control; user selects only B/C/D.
    if status == "chosen":
        competitor = next((lb for lb in chosen if lb != locked_label and lb in available_labels), None)
        if competitor:
            chosen_labels = [locked_label, competitor]
        else:
            chosen_labels = [locked_label]
        auto_picked = False
    else:
        # Auto-pick locked A and C, or locked A and B if C is unavailable.
        if locked_label in available_labels and "C" in available_labels:
            chosen_labels = [locked_label, "C"]
        elif locked_label in available_labels:
            competitor = next((lb for lb in available_labels if lb != locked_label), None)
            chosen_labels = [locked_label, competitor] if competitor else [locked_label]
        elif len(variants) >= 3:
            chosen_labels = [variants[0]["label"], variants[2]["label"]]
        else:
            chosen_labels = [v["label"] for v in variants[:2]]
        auto_picked = True

    chosen_variants = [v for v in variants if v["label"] in chosen_labels]
    if len(chosen_variants) < 2:
        fallback_competitor = next((v for v in variants if v["label"] != locked_label), None)
        locked_variant = next((v for v in variants if v["label"] == locked_label), None)
        chosen_variants = [v for v in [locked_variant, fallback_competitor] if v]
    if len(chosen_variants) < 2:
        print("[warn] cannot resolve two variants from labels {}".format(chosen_labels))
        return

    print("[pick] {}: {}".format("auto" if auto_picked else "user",
                                  [v["label"] for v in chosen_variants]))

    # Build variant_texts dict keyed by hypothesis name
    variant_texts = {}  # type: Dict[str, str]
    for v in chosen_variants:
        key = "FAR_{}_agent_gen".format(v["label"])
        variant_texts[key] = v["text"]

    batch_key      = next_batch_key()
    pilot_id_start = next_pilot_id_start()

    # Build batch CSVs
    try:
        contacts_csv, log_csv = build_next_batch(batch_key, variant_texts, pilot_id_start)
        print("[batch] {} -> {}".format(batch_key, contacts_csv.name))
        import csv as _csv
        with open(str(contacts_csv), encoding="utf-8-sig") as _f:
            n_contacts = sum(1 for _ in _csv.DictReader(_f))
    except RuntimeError as exc:
        print("[error] {}".format(exc))
        send_telegram(token, CHAT_ID,
                      "⚠️ AIMA агент: не вдалося побудувати батч {}: {}".format(batch_key, exc))
        return

    # Generate send script
    write_send_script(batch_key)

    # Update bot state
    state = {}
    if STATE_FILE.exists():
        state = json.loads(STATE_FILE.read_text(encoding="utf-8"))

    send_script = str(BASE_DIR / "src/aima_batch_send_{}.py".format(batch_key))
    gdrive_url  = (state.get("proposals", {}).get("v6") or {}).get("gdrive_url", "")
    state.setdefault("proposals", {})[batch_key] = {
        "name": "FAR {} — {} контактів (агент)".format(batch_key, n_contacts),
        "contacts_csv": str(contacts_csv),
        "log_csv":      str(log_csv),
        "send_script":  send_script,
        "gdrive_url":   gdrive_url,
    }
    state["active_batch"] = batch_key
    state["pending"]      = batch_key
    # Only reset daily if previous cycle is fully done (sent/postponed/rejected).
    # If status=approved or ping_sent, the current day's approval must be preserved.
    prev_daily_status = state.get("daily", {}).get("status", "idle")
    if prev_daily_status not in ("approved", "ping_sent"):
        state["daily"] = {
            "date": "", "status": "idle", "morning_msg_id": None,
            "send_triggered": False, "sent_count": None,
            "approved_by": None, "approved_at": None, "sent_at": None,
            "reminder_sent": False,
        }
    STATE_FILE.write_text(json.dumps(state, ensure_ascii=False, indent=2), encoding="utf-8")
    print("[state] active_batch={} (daily preserved={})".format(batch_key, prev_daily_status))

    # Finalize variants file
    data["status"]           = "finalized"
    data["finalized_labels"] = [v["label"] for v in chosen_variants]
    data["finalized_at"]     = datetime.now().isoformat()
    VARIANTS_FILE.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8")

    # Send group message
    auto_note = " (автовибір)" if auto_picked else ""
    chosen_info = "\n".join(
        "  <b>{}:</b> {}".format(v["label"], v["text"][:160])
        for v in chosen_variants
    )
    msg = (
        "\U0001f514 {}, підготовка наступної розсилки!\n\n"
        "Батч: <b>FAR {}</b> | Контактів: <b>{}</b>\n"
        "Гіпотези{}:\n{}\n\n"
        "Завтра о <b>08:00</b> бот нагадає для підтвердження.\n"
        "/status — поточний стан"
    ).format(os.environ.get("AIMA_MARKETER", MARKETER), batch_key, n_contacts, auto_note, chosen_info)

    result = send_telegram(token, CHAT_ID, msg)
    if result.get("ok"):
        print("[sent] group proposal for {}".format(batch_key))
    else:
        print("[warn] telegram error: {}".format(result))


if __name__ == "__main__":
    main()
