from __future__ import annotations

import argparse
import random
import sqlite3
import uuid
from datetime import datetime, timedelta
from pathlib import Path

from backend.database.store import SQLiteStore


def iso(dt: datetime) -> str:
    return dt.replace(microsecond=0).isoformat() + "Z"


def rand_between(start: datetime, end: datetime) -> datetime:
    total = (end - start).total_seconds()
    return start + timedelta(seconds=random.randint(0, int(total)))


def make_fake_users(n: int, prefix: str) -> list[str]:
    return [f"{prefix}{i:03d}" for i in range(1, n + 1)]


def sample_text(seed_kind: str, idx: int) -> str:
    if seed_kind == "user":
        return f"Hi, I’m trying case #{idx}. Can you help?"
    return f"Sure—here’s what I found for case #{idx}."


def ensure_tables(db_path: str):
    SQLiteStore(db_path)


def wipe_generated_data(db_path: str, keep_agents: bool, user_prefix: str):
    """
    Remove ONLY data created by the seeder:
      - conversations/messages for users whose id starts with user_prefix
      - preferences and drafts for those users
      - shares where principal starts with user_prefix
    Agents are kept by default and only dropped if keep_agents=False.
    """
    with sqlite3.connect(db_path) as cn:
        cn.execute("PRAGMA foreign_keys=ON")
        pref = f"{user_prefix}%"

        conv_ids = [
            row[0]
            for row in cn.execute("SELECT conversation_id FROM conversations WHERE user_id LIKE ?", (pref,)).fetchall()
        ]

        if conv_ids:
            placeholders = ",".join("?" * len(conv_ids))
            cn.execute(f"DELETE FROM messages WHERE conversation_id IN ({placeholders})", conv_ids)
            cn.execute(f"DELETE FROM conversations WHERE conversation_id IN ({placeholders})", conv_ids)

        cn.execute("DELETE FROM preferences WHERE user_id LIKE ?", (pref,))
        cn.execute("DELETE FROM draft_conversations WHERE user_id LIKE ?", (pref,))

        cn.execute("DELETE FROM agent_shares WHERE principal LIKE ?", (pref,))

        if not keep_agents:
            cn.execute("DELETE FROM agents")
        cn.commit()


def _mutate_selection(selection: list[str], pool: list[str], p_change: float = 0.35) -> list[str]:
    """
    Randomly change current selection: with probability p_change either
    remove one currently selected agent or add a new one from the pool.
    Returns a new list (does not mutate input).
    """
    sel = list(selection)
    if random.random() < p_change:
        if sel and random.random() < 0.5:
            # remove one
            sel.pop(random.randrange(len(sel)))
        else:
            # add one that isn't already selected
            candidates = [a for a in pool if a not in sel]
            if candidates:
                sel.append(random.choice(candidates))
    return sel


POSITIVE_COMMENTS = [
    "Super helpful!",
    "That worked, thanks.",
    "Exactly what I needed.",
    "Great answer.",
    "Nice and clear.",
]

NEGATIVE_COMMENTS = [
    "This missed the point.",
    "Still stuck after this.",
    "Not quite right.",
    "Confusing answer.",
    "Needs more detail.",
]


def make_feedback(positive_prob: float, with_comments_prob: float = 0.35) -> dict:
    """Return a feedback dict compatible with analytics code: {'rating': 1|0, 'comment': ...?}"""
    is_pos = random.random() < positive_prob
    fb = {"rating": 1 if is_pos else 0}
    if random.random() < with_comments_prob:
        fb["comment"] = random.choice(POSITIVE_COMMENTS if is_pos else NEGATIVE_COMMENTS)
    return fb


def seed(
    db_path: str,
    num_users: int,
    conv_per_user: int,
    min_msgs: int,
    max_msgs: int,
    start_date: str,
    end_date: str,
    share_probability: float,
    user_prefix: str,
    wipe_first: bool,
    keep_agents: bool,
    agents_limit: int | None,
    feedback_probability: float,
    positive_probability: float,
    selection_drift: float = 0.30,  # probability selection changes on each user turn
    max_used_per_turn: int = 2,
):
    db_path = str(Path(db_path).expanduser())
    ensure_tables(db_path)
    store = SQLiteStore(db_path)

    if wipe_first:
        wipe_generated_data(db_path, keep_agents=keep_agents, user_prefix=user_prefix)

    agents = store.get_all_agents()
    if not agents:
        raise SystemExit("No agents found. Create your test agents in the app first, then re-run seeder.")
    if agents_limit:
        agents = agents[:agents_limit]

    agent_ids = [a["id"] for a in agents]

    users = make_fake_users(num_users, user_prefix)

    for a in agents:
        share_set = []
        for u in users:
            if random.random() < share_probability:
                share_set.append({"principal": u, "type": "user"})
        store.replace_agent_shares(a["id"], share_set)

    start_dt = datetime.fromisoformat(start_date)
    end_dt = datetime.fromisoformat(end_date)

    for u in users:
        for c_idx in range(conv_per_user):
            conv_id = str(uuid.uuid4())
            k = random.randint(1, min(3, len(agent_ids)))
            conv_agent_ids = random.sample(agent_ids, k=k)

            m_count = random.randint(min_msgs, max_msgs)
            timestamps = sorted(rand_between(start_dt, end_dt) for _ in range(m_count))
            msgs = []
            role = "user"
            selection: list[str] = list(conv_agent_ids)
            for i, ts in enumerate(timestamps, start=1):
                msg_id = str(uuid.uuid4())
                created = iso(ts)
                if role == "user":
                    # On user turns, selection may change a bit
                    selection = _mutate_selection(selection, agent_ids, p_change=selection_drift)
                    msgs.append(
                        {
                            "id": msg_id,
                            "role": "user",
                            "content": sample_text("user", i),
                            "createdAt": created,
                            "eventLog": [],
                            "actions": {"seed": True},
                            "artifacts": [],
                        }
                    )
                    role = "assistant"
                else:
                    # Assistant turn:
                    # Decide which of the selected agents are actually used
                    used: list[str] = []
                    if selection:
                        cap = max(1, min(max_used_per_turn, len(selection)))
                        used = random.sample(selection, k=random.randint(1, cap))

                    # Build a minimal event log that also works with legacy parsers
                    ev = [
                        {
                            "eventKind": "AGENT_SELECTION",
                            "eventData": {"selection": list(selection)},
                        }
                    ]
                    # (Optional) lightweight "usage" events; harmless for current analytics
                    for aid in used:
                        ev.append({"eventKind": "AGENT_TOOL_START", "eventData": {"toolName": aid}})

                    # Attach both snake_case and camelCase fields for compatibility
                    msgs.append(
                        {
                            "id": msg_id,
                            "role": "assistant",
                            "content": sample_text("assistant", i),
                            "createdAt": created,
                            "eventLog": ev,
                            "actions": {"seed": True},
                            "artifacts": [],
                            "selectedAgentIds": list(selection),
                            "usedAgentIds": list(used),
                        }
                    )
                    role = "user"

            # Insert conversation + messages
            store.update_conversation(
                conv_id,
                {
                    "userId": u,
                    "title": f"Seeded conversation {c_idx + 1} for {u}",
                    "lastUpdated": iso(timestamps[-1]),
                    "agentIds": conv_agent_ids,
                    "messages": msgs,
                },
            )

            for m in msgs:
                if m["role"] != "assistant":
                    continue
                if random.random() < feedback_probability:
                    fb = make_feedback(positive_prob=positive_probability)
                    # seed into column-backed feedback
                    store.update_message_feedback(
                        m["id"],
                        rating=fb.get("rating"),
                        text=fb.get("comment"),
                        by=u,
                    )

    print(
        f"Seed complete: {len(users)} users, ~{conv_per_user} convs/user, "
        f"{min_msgs}-{max_msgs} msgs/conv across {start_date} → {end_date}. "
        f"Agents: {len(agent_ids)} | selection drift: {selection_drift} | max used/turn: {max_used_per_turn}"
    )


def main():
    ap = argparse.ArgumentParser(
        description="Seed the app's SQLite DB with fake users, shares, conversations, messages."
    )
    ap.add_argument("--db", required=True, help="Path to SQLite DB file (the one the app uses)")
    ap.add_argument("--users", type=int, default=25, help="Number of fake users to create")
    ap.add_argument("--convos-per-user", type=int, default=6, help="Conversations per user")
    ap.add_argument("--min-msgs", type=int, default=6, help="Min messages per conversation")
    ap.add_argument("--max-msgs", type=int, default=18, help="Max messages per conversation")
    ap.add_argument("--start", default="2024-01-01", help="ISO start date (YYYY-MM-DD)")
    ap.add_argument("--end", default="2025-09-01", help="ISO end date (YYYY-MM-DD)")
    ap.add_argument("--share-prob", type=float, default=0.35, help="Probability a user gets access to an agent")
    ap.add_argument("--user-prefix", default="seed_user_", help="Prefix for fake user logins")
    ap.add_argument("--wipe", action="store_true", help="Delete previous *seeded* data for --user-prefix first")
    ap.add_argument("--drop-agents", action="store_true", help="Also delete agents when wiping (not default)")
    ap.add_argument(
        "--keep-agents", action="store_true", default=True, help="(default) Keep agents table intact when wiping"
    )
    ap.add_argument("--agents-limit", type=int, default=None, help="Only use the first N agents (for quick tests)")
    ap.add_argument("--seed", type=int, default=None, help="Random seed for reproducibility")
    ap.add_argument("--feedback-prob", type=float, default=0.25, help="Prob. an assistant msg gets feedback")
    ap.add_argument("--positive-prob", type=float, default=0.7, help="Prob. feedback is positive when present")
    ap.add_argument(
        "--selection-drift", type=float, default=0.10, help="Probability that agent selection changes on user turns"
    )
    ap.add_argument("--max-used-per-turn", type=int, default=2, help="Maximum number of used agents per assistant turn")
    args = ap.parse_args()

    if args.seed is not None:
        random.seed(args.seed)

    seed(
        db_path=args.db,
        num_users=args.users,
        conv_per_user=args.convos_per_user,
        min_msgs=args.min_msgs,
        max_msgs=args.max_msgs,
        start_date=args.start,
        end_date=args.end,
        share_probability=args.share_prob,
        user_prefix=args.user_prefix,
        wipe_first=args.wipe,
        keep_agents=(not args.drop_agents),
        agents_limit=args.agents_limit,
        feedback_probability=args.feedback_prob,
        positive_probability=args.positive_prob,
        selection_drift=args.selection_drift,
        max_used_per_turn=args.max_used_per_turn,
    )


if __name__ == "__main__":
    main()
