from flask import Flask, render_template, request, redirect, url_for, jsonify
import sqlite3

app = Flask(__name__, static_folder="assets", static_url_path="/assets")
DB_PATH = "tarkov.db"


def get_db():
    conn = sqlite3.connect(DB_PATH)
    conn.row_factory = sqlite3.Row
    conn.execute("PRAGMA foreign_keys = ON")
    return conn


def _migrate_key_ids_and_maps():
    conn = sqlite3.connect(DB_PATH)
    conn.row_factory = sqlite3.Row
    conn.execute("PRAGMA foreign_keys = OFF")

    # Backfill missing key IDs with their api_id so ratings can join correctly.
    conn.execute("""
        UPDATE keys
        SET id = api_id
        WHERE id IS NULL AND api_id IS NOT NULL
    """)

    # If key_maps was created with INTEGER key_id, migrate to TEXT to match keys.id.
    cols = conn.execute("PRAGMA table_info(key_maps)").fetchall()
    key_id_type = None
    if cols:
        for col in cols:
            if col["name"] == "key_id":
                key_id_type = (col["type"] or "").upper()
                break
    if key_id_type and key_id_type != "TEXT":
        conn.execute("ALTER TABLE key_maps RENAME TO key_maps_old")
        conn.execute("""
            CREATE TABLE key_maps (
              key_id TEXT NOT NULL,
              map_id INTEGER NOT NULL,
              PRIMARY KEY (key_id, map_id),
              FOREIGN KEY (key_id) REFERENCES keys(id),
              FOREIGN KEY (map_id) REFERENCES maps(id)
            )
        """)
        conn.execute("""
            INSERT OR IGNORE INTO key_maps (key_id, map_id)
            SELECT CAST(key_id AS TEXT), map_id
            FROM key_maps_old
            WHERE key_id IS NOT NULL
              AND EXISTS (SELECT 1 FROM keys WHERE id = CAST(key_id AS TEXT))
        """)
        conn.execute("DROP TABLE key_maps_old")

    # Remove orphaned ratings created with "None" or missing keys.
    conn.execute("""
        DELETE FROM key_ratings
        WHERE key_id IS NULL
           OR key_id = 'None'
           OR key_id NOT IN (SELECT id FROM keys)
    """)

    conn.commit()
    conn.execute("PRAGMA foreign_keys = ON")
    conn.close()


_migrate_key_ids_and_maps()


@app.route("/")
def landing():
    return render_template("landing.html")


def _keys_context():
    conn = get_db()
    maps = conn.execute("SELECT id, name FROM maps ORDER BY name").fetchall()
    map_filter = request.args.get("map_id", type=int)
    sort = request.args.get("sort", "priority_desc")
    show = request.args.get("show", "all")
    key_map_rows = conn.execute("SELECT key_id, map_id FROM key_maps").fetchall()
    key_maps = {}
    for row in key_map_rows:
        key_maps.setdefault(row["key_id"], set()).add(row["map_id"])

    key_query = """
        SELECT
            k.id,
            k.name,
            k.icon_url,
            k.grid_image_url,
            k.wiki_url,
            r.priority,
            r.reason,
            COALESCE(r.used_in_quest, 0) AS used_in_quest
        FROM keys k
    """
    params = []
    if map_filter:
        key_query += """
        JOIN key_maps kmf
            ON k.id = kmf.key_id
           AND kmf.map_id = ?
        """
        params.append(map_filter)
    key_query += " LEFT JOIN key_ratings r ON k.id = r.key_id "
    if show == "rated":
        key_query += " WHERE r.priority IS NOT NULL "
    elif show == "unrated":
        key_query += " WHERE r.priority IS NULL "
    elif show == "quest":
        key_query += " WHERE COALESCE(r.used_in_quest, 0) = 1 "

    if sort == "name_asc":
        order_by = "k.name ASC"
    elif sort == "name_desc":
        order_by = "k.name DESC"
    elif sort == "priority_asc":
        order_by = "CASE WHEN r.priority IS NULL THEN 1 ELSE 0 END, r.priority ASC, k.name"
    else:
        order_by = "CASE WHEN r.priority IS NULL THEN 1 ELSE 0 END, r.priority DESC, k.name"

    key_query += f" ORDER BY {order_by} "
    keys = conn.execute(key_query, params).fetchall()

    key_maps = {k: sorted(v) for k, v in key_maps.items()}

    # Build info view data: all keys (unfiltered), grouped by map then unassigned.
    all_keys = conn.execute("""
        SELECT k.id, k.name, k.grid_image_url, k.wiki_url,
               r.priority, r.reason, COALESCE(r.used_in_quest, 0) AS used_in_quest
        FROM keys k
        LEFT JOIN key_ratings r ON k.id = r.key_id
        ORDER BY k.name ASC
    """).fetchall()
    conn.close()

    # keys_by_map: list of (map_row, [key_row, ...]) sorted by map name
    keys_by_map = []
    for m in sorted(maps, key=lambda r: r["name"]):
        bucket = [k for k in all_keys if m["id"] in key_maps.get(k["id"], [])]
        keys_by_map.append((m, bucket))
    unassigned_keys = [k for k in all_keys if not key_maps.get(k["id"])]

    return dict(keys=keys, maps=maps, key_maps=key_maps,
                map_filter=map_filter, sort=sort, show=show,
                keys_by_map=keys_by_map, unassigned_keys=unassigned_keys)


@app.route("/keys")
def keys_page():
    return render_template("keys.html", **_keys_context())


@app.route("/rate", methods=["POST"])
def rate_key():
    key_id = request.form["key_id"]
    priority = request.form.get("priority")
    if priority == "":
        priority = None
    reason = request.form.get("reason", "")
    used_in_quest = 1 if request.form.get("used_in_quest") == "on" else 0
    map_filter = request.form.get("map_id")
    sort = request.form.get("sort")
    show = request.form.get("show")
    map_ids = []
    for value in request.form.getlist("map_ids"):
        try:
            map_ids.append(int(value))
        except ValueError:
            continue

    conn = get_db()
    conn.execute("""
        INSERT INTO key_ratings (key_id, priority, reason, used_in_quest)
        VALUES (?, ?, ?, ?)
        ON CONFLICT(key_id) DO UPDATE SET
            priority = excluded.priority,
            reason = excluded.reason,
            used_in_quest = excluded.used_in_quest,
            updated_at = CURRENT_TIMESTAMP
    """, (key_id, priority, reason, used_in_quest))
    conn.execute("DELETE FROM key_maps WHERE key_id = ?", (key_id,))
    if map_ids:
        conn.executemany(
            "INSERT OR IGNORE INTO key_maps (key_id, map_id) VALUES (?, ?)",
            [(key_id, map_id) for map_id in map_ids],
        )
    conn.commit()
    conn.close()

    redirect_args = {}
    if map_filter:
        redirect_args["map_id"] = map_filter
    if sort:
        redirect_args["sort"] = sort
    if show:
        redirect_args["show"] = show
    base_url = url_for("keys_page", **redirect_args)
    return redirect(f"{base_url}#key-{key_id}")


@app.route("/rate_json", methods=["POST"])
def rate_json():
    data = request.get_json(force=True)
    key_id = data.get("key_id")
    if not key_id:
        return jsonify({"ok": False, "error": "missing key_id"}), 400
    priority = data.get("priority")  # None or int
    reason = data.get("reason", "")
    used_in_quest = 1 if data.get("used_in_quest") else 0
    map_ids = [int(m) for m in data.get("map_ids", [])]
    conn = get_db()
    _update_key(conn, key_id, priority, reason, used_in_quest, map_ids)
    conn.commit()
    conn.close()
    return jsonify({"ok": True})


def _update_key(conn, key_id, priority, reason, used_in_quest, map_ids):
    conn.execute("""
        INSERT INTO key_ratings (key_id, priority, reason, used_in_quest)
        VALUES (?, ?, ?, ?)
        ON CONFLICT(key_id) DO UPDATE SET
            priority = excluded.priority,
            reason = excluded.reason,
            used_in_quest = excluded.used_in_quest,
            updated_at = CURRENT_TIMESTAMP
    """, (key_id, priority, reason, used_in_quest))
    conn.execute("DELETE FROM key_maps WHERE key_id = ?", (key_id,))
    if map_ids:
        conn.executemany(
            "INSERT OR IGNORE INTO key_maps (key_id, map_id) VALUES (?, ?)",
            [(key_id, map_id) for map_id in map_ids],
        )


@app.route("/rate_all", methods=["POST"])
def rate_all():
    key_ids = request.form.getlist("key_ids")
    save_one = request.form.get("save_one")
    map_filter = request.form.get("map_id")
    sort = request.form.get("sort")
    show = request.form.get("show")

    if save_one:
        key_ids = [save_one]

    conn = get_db()
    for key_id in key_ids:
        priority = request.form.get(f"priority_{key_id}")
        if priority is None:
            continue
        if priority == "":
            priority = None
        reason = request.form.get(f"reason_{key_id}", "")
        used_in_quest = 1 if request.form.get(f"used_in_quest_{key_id}") == "on" else 0
        map_ids = []
        for value in request.form.getlist(f"map_ids_{key_id}"):
            try:
                map_ids.append(int(value))
            except ValueError:
                continue
        _update_key(conn, key_id, priority, reason, used_in_quest, map_ids)
    conn.commit()
    conn.close()

    redirect_args = {}
    if map_filter:
        redirect_args["map_id"] = map_filter
    if sort:
        redirect_args["sort"] = sort
    if show:
        redirect_args["show"] = show
    base_url = url_for("keys_page", **redirect_args)
    if save_one:
        return redirect(f"{base_url}#key-{save_one}")
    return redirect(base_url)


@app.route("/quests")
def quests():
    conn = get_db()
    only_collector = request.args.get("collector") == "1"
    view = request.args.get("view", "flow")  # "flow" or "list"

    # All quests + done state
    all_quests = conn.execute("""
        SELECT q.id, q.name, q.trader, q.wiki_link,
               COALESCE(qp.done, 0) AS done
        FROM quests q
        LEFT JOIN quest_progress qp ON q.id = qp.quest_id
        ORDER BY q.trader, q.name
    """).fetchall()

    # All dependency edges
    all_deps = conn.execute("SELECT quest_id, depends_on FROM quest_deps").fetchall()

    # Collector prereq set
    collector_row = conn.execute("SELECT id FROM quests WHERE name = 'Collector'").fetchone()
    collector_prereqs = set()
    if collector_row:
        rows = conn.execute("""
            WITH RECURSIVE deps(quest_id) AS (
                SELECT depends_on FROM quest_deps WHERE quest_id = ?
                UNION
                SELECT qd.depends_on FROM quest_deps qd
                JOIN deps d ON qd.quest_id = d.quest_id
            )
            SELECT quest_id FROM deps
        """, (collector_row["id"],)).fetchall()
        collector_prereqs = {r[0] for r in rows}

    conn.close()

    # Build lookup structures
    quest_by_id = {q["id"]: q for q in all_quests}
    # children[parent_id] = [child_id, ...]  (child depends_on parent)
    children = {}
    parents = {}
    for dep in all_deps:
        child, parent = dep["quest_id"], dep["depends_on"]
        children.setdefault(parent, []).append(child)
        parents.setdefault(child, []).append(parent)
    # Sort each child list by quest name
    for parent_id in children:
        children[parent_id].sort(key=lambda i: quest_by_id[i]["name"] if i in quest_by_id else "")

    # Filter to collector-only if requested
    if only_collector:
        visible = set(collector_prereqs)
    else:
        visible = set(quest_by_id.keys())

    # Root quests: in visible set and have no parents also in visible set
    roots = [
        qid for qid in visible
        if not any(p in visible for p in parents.get(qid, []))
    ]

    # Group roots by trader, sorted
    trader_roots = {}
    for qid in sorted(roots, key=lambda i: (quest_by_id[i]["trader"], quest_by_id[i]["name"])):
        t = quest_by_id[qid]["trader"]
        trader_roots.setdefault(t, []).append(qid)

    traders = sorted(trader_roots.keys())

    return render_template(
        "quests.html",
        quest_by_id=quest_by_id,
        children=children,
        trader_roots=trader_roots,
        traders=traders,
        visible=visible,
        collector_prereqs=collector_prereqs,
        only_collector=only_collector,
        view=view,
    )


@app.route("/collector")
def collector():
    conn = get_db()
    view = request.args.get("view", "flow")
    collector = conn.execute(
        "SELECT id FROM quests WHERE name = 'Collector'"
    ).fetchone()

    if not collector:
        conn.close()
        return "Run import_quests.py first to populate quest data.", 503

    # All quests + done state
    all_quests = conn.execute("""
        SELECT q.id, q.name, q.trader, q.wiki_link,
               COALESCE(qp.done, 0) AS done
        FROM quests q
        LEFT JOIN quest_progress qp ON q.id = qp.quest_id
        ORDER BY q.trader, q.name
    """).fetchall()

    # All dependency edges
    all_deps = conn.execute("SELECT quest_id, depends_on FROM quest_deps").fetchall()

    # Collector prereq set (transitive)
    rows = conn.execute("""
        WITH RECURSIVE deps(quest_id) AS (
            SELECT depends_on FROM quest_deps WHERE quest_id = ?
            UNION
            SELECT qd.depends_on FROM quest_deps qd
            JOIN deps d ON qd.quest_id = d.quest_id
        )
        SELECT quest_id FROM deps
    """, (collector["id"],)).fetchall()
    collector_prereqs = {r[0] for r in rows}

    conn.close()

    # Build lookup structures
    quest_by_id = {q["id"]: q for q in all_quests}
    # children[parent_id] = [child_id, ...]  (child depends_on parent)
    children = {}
    parents = {}
    for dep in all_deps:
        child, parent = dep["quest_id"], dep["depends_on"]
        children.setdefault(parent, []).append(child)
        parents.setdefault(child, []).append(parent)
    # Sort each child list by quest name
    for parent_id in children:
        children[parent_id].sort(key=lambda i: quest_by_id[i]["name"] if i in quest_by_id else "")

    visible = set(collector_prereqs)

    # Root quests: in visible set and have no parents also in visible set
    roots = [
        qid for qid in visible
        if not any(p in visible for p in parents.get(qid, []))
    ]

    # Group roots by trader, sorted
    trader_roots = {}
    for qid in sorted(roots, key=lambda i: (quest_by_id[i]["trader"], quest_by_id[i]["name"])):
        t = quest_by_id[qid]["trader"]
        trader_roots.setdefault(t, []).append(qid)

    traders = sorted(trader_roots.keys())

    total = len(collector_prereqs)
    done = sum(1 for qid in collector_prereqs if qid in quest_by_id and quest_by_id[qid]["done"])
    return render_template(
        "collector.html",
        quest_by_id=quest_by_id,
        children=children,
        trader_roots=trader_roots,
        traders=traders,
        visible=visible,
        collector_prereqs=collector_prereqs,
        collector_id=collector["id"],
        total=total,
        done=done,
        view=view,
    )


@app.route("/collector/toggle", methods=["POST"])
def collector_toggle():
    quest_id = request.form["quest_id"]
    done = 1 if request.form.get("done") == "1" else 0
    conn = get_db()
    conn.execute("""
        INSERT INTO quest_progress (quest_id, done) VALUES (?, ?)
        ON CONFLICT(quest_id) DO UPDATE SET done = excluded.done
    """, (quest_id, done))
    conn.commit()
    conn.close()
    return jsonify({"quest_id": quest_id, "done": done})


# --- Loadout planner helpers ---

# Known user-facing slot filters: (label, slot_nameid)
LOADOUT_SLOT_FILTERS = [
    ("Suppressor",  "mod_muzzle"),
    ("Scope",       "mod_scope"),
    ("Flashlight",  "mod_tactical"),
    ("Stock",       "mod_stock"),
    ("Foregrip",    "mod_foregrip"),
]

def _sort_col(sort):
    return {
        "weight_asc":        "weight_kg ASC NULLS LAST",
        "weight_desc":       "weight_kg DESC NULLS LAST",
        "name_asc":          "name ASC",
        "name_desc":         "name DESC",
        "class_desc":        "armor_class DESC NULLS LAST, weight_kg ASC NULLS LAST",
        "class_asc":         "armor_class ASC NULLS LAST, weight_kg ASC NULLS LAST",
        "capacity_desc":     "capacity DESC NULLS LAST, weight_kg ASC NULLS LAST",
        "capacity_asc":      "capacity ASC NULLS LAST, weight_kg ASC NULLS LAST",
        # carry_efficiency sorts are handled in Python after query; fall back to weight
        "efficiency_desc":   "weight_kg ASC NULLS LAST",
        "efficiency_asc":    "weight_kg ASC NULLS LAST",
    }.get(sort, "weight_kg ASC NULLS LAST")


def _carry_efficiency(weight_kg, slot_count):
    """Return (slots_per_kg, kg_per_slot) or (None, None) if inputs are invalid."""
    if not weight_kg or not slot_count:
        return None, None
    try:
        w = float(weight_kg)
        s = int(slot_count)
    except (TypeError, ValueError):
        return None, None
    if w <= 0 or s <= 0:
        return None, None
    return round(s / w, 2), round(w / s, 3)


def _enrich_with_efficiency(rows):
    """Attach slots_per_kg and kg_per_slot to each sqlite3.Row (returns plain dicts)."""
    enriched = []
    for row in rows:
        d = dict(row)
        d["slots_per_kg"], d["kg_per_slot"] = _carry_efficiency(
            d.get("weight_kg"), d.get("capacity")
        )
        enriched.append(d)
    return enriched


def _sort_enriched(rows, sort):
    """Sort a list of enriched dicts by carry efficiency when requested."""
    if sort == "efficiency_desc":
        return sorted(rows, key=lambda r: (r["slots_per_kg"] is None, -(r["slots_per_kg"] or 0)))
    if sort == "efficiency_asc":
        return sorted(rows, key=lambda r: (r["slots_per_kg"] is None, r["slots_per_kg"] or 0))
    return rows


@app.route("/loadout")
def loadout():
    conn = get_db()
    tab  = request.args.get("tab", "guns")
    sort = request.args.get("sort", "weight_asc")

    guns = armor = helmets = headwear = backpacks = rigs = armored_rigs = plates = []
    builder_guns = builder_armor = builder_helmets = builder_rigs = builder_backpacks = []
    requires = request.args.getlist("requires")  # list of slot_nameids that must exist
    min_class    = request.args.get("min_class",    0, type=int)
    min_capacity = request.args.get("min_capacity", 0, type=int)

    sort_frag = _sort_col(sort)

    if tab == "guns":
        if requires:
            placeholders = ",".join("?" * len(requires))
            # Gun must have ALL required slots; compute lightest build weight
            guns = conn.execute(f"""
                SELECT g.*,
                    (g.weight_kg + COALESCE((
                        SELECT SUM(s.min_w) FROM (
                            SELECT gs.slot_id, MIN(m.weight_kg) AS min_w
                            FROM gun_slots gs
                            JOIN gun_slot_items gsi
                                ON gsi.gun_id = gs.gun_id AND gsi.slot_id = gs.slot_id
                            JOIN gear_items m ON m.id = gsi.item_id
                            WHERE gs.gun_id = g.id
                              AND gs.slot_nameid IN ({placeholders})
                              AND m.weight_kg IS NOT NULL
                            GROUP BY gs.slot_id
                        ) s
                    ), 0.0)) AS lightest_build_weight
                FROM gear_items g
                WHERE g.category = 'gun'
                  AND (
                    SELECT COUNT(DISTINCT gs2.slot_nameid)
                    FROM gun_slots gs2
                    WHERE gs2.gun_id = g.id
                      AND gs2.slot_nameid IN ({placeholders})
                  ) = ?
                ORDER BY lightest_build_weight ASC NULLS LAST
            """, requires + requires + [len(requires)]).fetchall()
        else:
            guns = conn.execute(f"""
                SELECT *, weight_kg AS lightest_build_weight
                FROM gear_items
                WHERE category = 'gun'
                ORDER BY {sort_frag}
            """).fetchall()

    elif tab == "armor":
        armor = conn.execute(f"""
            SELECT * FROM gear_items
            WHERE category = 'armor'
              AND (? = 0 OR armor_class >= ?)
            ORDER BY {sort_frag}
        """, (min_class, min_class)).fetchall()

    elif tab == "helmets":
        helmets = conn.execute(f"""
            SELECT * FROM gear_items
            WHERE category = 'helmet'
              AND (? = 0 OR armor_class >= ?)
            ORDER BY {sort_frag}
        """, (min_class, min_class)).fetchall()

    elif tab == "headwear":
        headwear = conn.execute(f"""
            SELECT * FROM gear_items
            WHERE category = 'headwear'
              AND (? = 0 OR armor_class >= ?)
            ORDER BY {sort_frag}
        """, (min_class, min_class)).fetchall()

    elif tab == "backpacks":
        rows = conn.execute(f"""
            SELECT * FROM gear_items
            WHERE category = 'backpack'
              AND (? = 0 OR capacity >= ?)
            ORDER BY {sort_frag}
        """, (min_capacity, min_capacity)).fetchall()
        backpacks = _sort_enriched(_enrich_with_efficiency(rows), sort)

    elif tab == "rigs":
        rows = conn.execute(f"""
            SELECT * FROM gear_items
            WHERE category = 'rig'
              AND armor_class IS NULL
              AND (? = 0 OR capacity >= ?)
            ORDER BY {sort_frag}
        """, (min_capacity, min_capacity)).fetchall()
        rigs = _sort_enriched(_enrich_with_efficiency(rows), sort)

    elif tab == "armored_rigs":
        rows = conn.execute(f"""
            SELECT * FROM gear_items
            WHERE category = 'rig'
              AND armor_class IS NOT NULL
              AND (? = 0 OR capacity >= ?)
              AND (? = 0 OR armor_class >= ?)
            ORDER BY {sort_frag}
        """, (min_capacity, min_capacity, min_class, min_class)).fetchall()
        armored_rigs = _sort_enriched(_enrich_with_efficiency(rows), sort)

    elif tab == "plates":
        plates = conn.execute(f"""
            SELECT * FROM gear_items
            WHERE category = 'plate'
              AND (? = 0 OR armor_class >= ?)
            ORDER BY {sort_frag}
        """, (min_class, min_class)).fetchall()

    elif tab == "builder":
        builder_guns      = conn.execute("SELECT id, name, weight_kg FROM gear_items WHERE category='gun'      ORDER BY name").fetchall()
        builder_armor     = conn.execute("SELECT id, name, weight_kg FROM gear_items WHERE category='armor'    ORDER BY name").fetchall()
        builder_helmets   = conn.execute("SELECT id, name, weight_kg FROM gear_items WHERE category='helmet'   ORDER BY name").fetchall()
        builder_rigs      = conn.execute("SELECT id, name, weight_kg FROM gear_items WHERE category='rig'      ORDER BY name").fetchall()
        builder_backpacks = conn.execute("SELECT id, name, weight_kg FROM gear_items WHERE category='backpack' ORDER BY name").fetchall()

    # IDs of carriers that have at least one open plate slot (shell weight only)
    open_slot_rows = conn.execute("SELECT DISTINCT carrier_id FROM armor_open_slots").fetchall()
    carrier_ids_with_open_slots = {row["carrier_id"] for row in open_slot_rows}

    conn.close()
    return render_template(
        "loadout.html",
        tab=tab, sort=sort,
        guns=guns, armor=armor, helmets=helmets, headwear=headwear,
        backpacks=backpacks, rigs=rigs, armored_rigs=armored_rigs, plates=plates,
        slot_filters=LOADOUT_SLOT_FILTERS,
        requires=requires,
        min_class=min_class, min_capacity=min_capacity,
        builder_guns=builder_guns,
        builder_armor=builder_armor,
        builder_helmets=builder_helmets,
        builder_rigs=builder_rigs,
        builder_backpacks=builder_backpacks,
        carrier_ids_with_open_slots=carrier_ids_with_open_slots,
    )


@app.route("/loadout/gun/<gun_id>")
def gun_detail(gun_id):
    conn = get_db()
    gun = conn.execute(
        "SELECT * FROM gear_items WHERE id = ? AND category = 'gun'", (gun_id,)
    ).fetchone()
    if not gun:
        conn.close()
        return "Gun not found.", 404

    # All slots for this gun, with every compatible mod sorted by weight
    slots_raw = conn.execute("""
        SELECT gs.slot_id, gs.slot_name, gs.slot_nameid, gs.required,
               m.id AS mod_id, m.name AS mod_name, m.short_name AS mod_short,
               m.weight_kg, m.grid_image_url, m.wiki_url, m.mod_type
        FROM gun_slots gs
        LEFT JOIN gun_slot_items gsi ON gsi.gun_id = gs.gun_id AND gsi.slot_id = gs.slot_id
        LEFT JOIN gear_items m ON m.id = gsi.item_id
        WHERE gs.gun_id = ?
        ORDER BY gs.slot_name, m.weight_kg ASC NULLS LAST
    """, (gun_id,)).fetchall()

    # Group by slot
    slots = {}
    slot_order = []
    for row in slots_raw:
        sid = row["slot_id"]
        if sid not in slots:
            slots[sid] = {
                "slot_id":   sid,
                "slot_name": row["slot_name"],
                "slot_nameid": row["slot_nameid"],
                "required":  row["required"],
                "mods":      [],
            }
            slot_order.append(sid)
        if row["mod_id"]:
            slots[sid]["mods"].append(dict(row))

    # Split into required vs optional slots
    KEY_SLOTS = {"mod_muzzle", "mod_magazine"}
    ordered_slots = [slots[s] for s in slot_order]
    # Required slots (always needed) shown at top — key slots (magazine/muzzle) highlighted
    key_slots      = [s for s in ordered_slots if s["required"] and s["slot_nameid"] in KEY_SLOTS]
    req_slots      = [s for s in ordered_slots if s["required"] and s["slot_nameid"] not in KEY_SLOTS]
    optional_slots = [s for s in ordered_slots if not s["required"]]

    # Lightest total (base + lightest per REQUIRED slot only)
    lightest_total = (gun["weight_kg"] or 0) + sum(
        s["mods"][0]["weight_kg"]
        for s in ordered_slots
        if s["required"] and s["mods"] and s["mods"][0]["weight_kg"] is not None
    )

    conn.close()
    return render_template(
        "gun_detail.html",
        gun=gun,
        key_slots=key_slots,
        req_slots=req_slots,
        optional_slots=optional_slots,
        lightest_total=lightest_total,
    )


@app.route("/loadout/gun/<gun_id>/slots.json")
def gun_slots_json(gun_id):
    """Returns slot summary for the expandable row (lightest mod per slot only)."""
    conn = get_db()
    rows = conn.execute("""
        SELECT gs.slot_name, gs.slot_nameid, gs.required,
               m.name AS mod_name, m.weight_kg
        FROM gun_slots gs
        LEFT JOIN (
            SELECT gsi.gun_id, gsi.slot_id,
                   m2.name, m2.weight_kg
            FROM gun_slot_items gsi
            JOIN gear_items m2 ON m2.id = gsi.item_id
            WHERE gsi.gun_id = ?
              AND m2.weight_kg = (
                  SELECT MIN(m3.weight_kg) FROM gun_slot_items gsi3
                  JOIN gear_items m3 ON m3.id = gsi3.item_id
                  WHERE gsi3.gun_id = gsi.gun_id AND gsi3.slot_id = gsi.slot_id
                    AND m3.weight_kg IS NOT NULL
              )
            GROUP BY gsi.slot_id
        ) m ON m.gun_id = gs.gun_id AND m.slot_id = gs.slot_id
        WHERE gs.gun_id = ?
        ORDER BY gs.slot_name
    """, (gun_id, gun_id)).fetchall()
    conn.close()

    KEY_SLOTS = {"mod_muzzle", "mod_magazine"}
    result = [
        {
            "slot_name":   r["slot_name"],
            "slot_nameid": r["slot_nameid"],
            "required":    r["required"],
            "mod_name":    r["mod_name"],
            "weight_kg":   r["weight_kg"],
            "key":         r["slot_nameid"] in KEY_SLOTS,
        }
        for r in rows
    ]
    return jsonify(result)


@app.route("/loadout/carrier/<carrier_id>/slots.json")
def carrier_slots_json(carrier_id):
    """Returns open plate slots and allowed plates for a carrier (armor or rig)."""
    conn = get_db()
    rows = conn.execute("""
        SELECT aos.slot_nameid, aos.zones,
               p.id AS plate_id, p.name AS plate_name, p.short_name AS plate_short,
               p.weight_kg, p.armor_class, p.durability, p.material
        FROM armor_open_slots aos
        LEFT JOIN armor_slot_plates asp ON asp.carrier_id = aos.carrier_id
            AND asp.slot_nameid = aos.slot_nameid
        LEFT JOIN gear_items p ON p.id = asp.plate_id
        WHERE aos.carrier_id = ?
        ORDER BY aos.slot_nameid, p.armor_class DESC, p.weight_kg ASC
    """, (carrier_id,)).fetchall()
    conn.close()

    # Group by slot
    slots = {}
    slot_order = []
    for row in rows:
        sn = row["slot_nameid"]
        if sn not in slots:
            slots[sn] = {"slot_nameid": sn, "zones": row["zones"], "plates": []}
            slot_order.append(sn)
        if row["plate_id"]:
            slots[sn]["plates"].append({
                "id":         row["plate_id"],
                "name":       row["plate_name"],
                "short_name": row["plate_short"],
                "weight_kg":  row["weight_kg"],
                "armor_class": row["armor_class"],
                "durability": row["durability"],
                "material":   row["material"],
            })

    return jsonify([slots[s] for s in slot_order])


@app.route("/loadout/save-build", methods=["POST"])
def save_build():
    data = request.get_json() or {}
    name        = (data.get("name") or "My Build").strip() or "My Build"
    gun_id      = data.get("gun_id") or None
    armor_id    = data.get("armor_id") or None
    helmet_id   = data.get("helmet_id") or None
    rig_id      = data.get("rig_id") or None
    backpack_id = data.get("backpack_id") or None
    notes       = data.get("notes", "")

    conn = get_db()
    cur = conn.execute("""
        INSERT INTO saved_builds (name, gun_id, armor_id, helmet_id, rig_id, backpack_id, notes)
        VALUES (?, ?, ?, ?, ?, ?, ?)
    """, (name, gun_id, armor_id, helmet_id, rig_id, backpack_id, notes))
    build_id = cur.lastrowid
    conn.commit()
    conn.close()
    return jsonify({"build_id": build_id, "name": name})


@app.route("/meds")
def meds():
    import requests as _req

    API_URL = "https://api.tarkov.dev/graphql"
    query = """
    {
      items(types: [injectors], lang: en) {
        id
        name
        shortName
        iconLink
        wikiLink
        properties {
          __typename
          ... on ItemPropertiesStim {
            useTime
            cures
            stimEffects {
              type
              skill { name }
              value
              percent
              duration
              delay
              chance
            }
          }
        }
      }
    }
    """
    try:
        resp = _req.post(API_URL, json={"query": query}, timeout=8)
        raw_items = resp.json()["data"]["items"]
    except Exception:
        raw_items = []

    # ── helper: pick first matching effect ──────────────────────────────
    def pick(effects, type_str, positive_only=False, negative_only=False):
        for e in effects:
            if e["type"] == type_str:
                if positive_only and e["value"] <= 0:
                    continue
                if negative_only and e["value"] >= 0:
                    continue
                return e
        return None

    def pick_skill(effects, skill_name):
        for e in effects:
            if e["type"] == "Skill" and e.get("skill") and e["skill"]["name"] == skill_name:
                return e
        return None

    # ── collect all skill names across all injectors ──────────────────
    all_skills = []
    for item in raw_items:
        p = item.get("properties") or {}
        for e in p.get("stimEffects", []):
            if e["type"] == "Skill" and e.get("skill"):
                sn = e["skill"]["name"]
                if sn not in all_skills:
                    all_skills.append(sn)
    skill_rows = sorted(all_skills)

    # ── build injector data rows ──────────────────────────────────────
    injectors = []
    for item in raw_items:
        p = item.get("properties") or {}
        effs = p.get("stimEffects", [])

        def _val(eff): return round(eff["value"], 2) if eff else None
        def _dur(eff): return eff["duration"] if eff else 0
        def _delay(eff): return eff["delay"] if eff else 0

        hp_e   = pick(effs, "Health regeneration", positive_only=True)
        stam_e = pick(effs, "Stamina recovery", positive_only=True)
        stam_neg_e = pick(effs, "Stamina recovery", negative_only=True)
        stam_rec_e = stam_e or stam_neg_e
        maxstam_e  = pick(effs, "Max stamina")
        weight_e   = pick(effs, "Weight limit")
        energy_e   = pick(effs, "Energy recovery")
        hydra_e    = pick(effs, "Hydration recovery")
        bleed_e    = pick(effs, "Stops and prevents bleedings")
        anti_e     = pick(effs, "Antidote")
        tremor_e   = pick(effs, "Hands tremor")
        tunnel_e   = pick(effs, "Tunnel effect")
        pain_e     = pick(effs, "Pain")
        temp_e     = pick(effs, "Body temperature")

        # skills dict
        skills = {}
        for sn in skill_rows:
            se = pick_skill(effs, sn)
            if se:
                skills[sn] = {"value": round(se["value"], 1), "duration": se["duration"]}

        # tags for column filtering
        tags = []
        if hp_e or bleed_e or anti_e:
            tags.append("heal")
        if maxstam_e or stam_rec_e:
            tags.append("stam")
        if skills:
            tags.append("skill")
        if temp_e or weight_e:
            tags.append("special")
        if not tags:
            tags.append("special")

        injectors.append({
            "name": item["name"],
            "short": item["shortName"],
            "icon": item.get("iconLink"),
            "wiki": item.get("wikiLink"),
            "tags": ",".join(tags),
            # healing
            "hp_regen":     _val(hp_e),
            "hp_regen_dur": _dur(hp_e),
            "stops_bleed":  bool(bleed_e),
            "antidote":     bool(anti_e),
            # stamina
            "max_stam":     _val(maxstam_e),
            "max_stam_dur": _dur(maxstam_e),
            "stam_rec":     _val(stam_rec_e),
            "stam_rec_dur": _dur(stam_rec_e),
            # weight
            "weight":     _val(weight_e),
            "weight_dur": _dur(weight_e),
            # special
            "body_temp":     _val(temp_e),
            "body_temp_dur": _dur(temp_e),
            "energy":        round(energy_e["value"], 2) if energy_e else None,
            "energy_dur":    _dur(energy_e),
            "hydration":     round(hydra_e["value"], 2) if hydra_e else None,
            "hydration_dur": _dur(hydra_e),
            # skills
            "skills": skills,
            # side effects
            "tremor":       bool(tremor_e),
            "tremor_delay": _delay(tremor_e),
            "tremor_dur":   _dur(tremor_e),
            "tunnel":       bool(tunnel_e),
            "tunnel_delay": _delay(tunnel_e),
            "tunnel_dur":   _dur(tunnel_e),
            "pain":         bool(pain_e),
            "pain_delay":   _delay(pain_e),
            "pain_dur":     _dur(pain_e),
        })

    # ── situation guide ───────────────────────────────────────────────
    situations = {
        "bleed": [
            {"short": "Zagustin",   "desc": "Stops bleeding, +Vitality 180s", "warn": "tremors delayed"},
            {"short": "AHF1-M",     "desc": "Stops bleeding, +Health 60s",     "warn": "-hydration"},
            {"short": "Perfotoran", "desc": "Stops bleed + antidote + regen",  "warn": "-energy after"},
            {"short": "xTG-12",     "desc": "Antidote only (no bleed stop)",   "warn": "-Health skill"},
        ],
        "regen": [
            {"short": "eTG-c",    "desc": "+6.5 HP/s for 60s (fast burst)",  "warn": "-energy after"},
            {"short": "Adrenaline","desc": "+4 HP/s for 15s, stam boost",    "warn": "-hydration after"},
            {"short": "PNB",      "desc": "+3 HP/s for 40s, +Strength",      "warn": "tremors + skill debuff"},
            {"short": "Propital", "desc": "+1 HP/s for 300s, skill buffs",   "warn": "tremors at 270s"},
            {"short": "Perfotoran","desc": "+1.5 HP/s for 60s + antidote",   "warn": "-energy after"},
        ],
        "stam": [
            {"short": "Trimadol",  "desc": "+3 stam rec, +10 max stam, 180s", "warn": "-energy/-hydration"},
            {"short": "SJ6",       "desc": "+2 stam rec, +30 max stam, 240s", "warn": "tremors + tunnel after"},
            {"short": "Meldonin",  "desc": "+0.5 stam rec, +Endurance 900s",  "warn": "-hydration/-energy (minor)"},
            {"short": "L1",        "desc": "+30 max stam, +Strength, 120s",   "warn": "-hydration/-energy"},
            {"short": "SJ1",       "desc": "+Endurance/Strength 180s",        "warn": "-energy/-hydration after"},
            {"short": "Adrenaline","desc": "Short burst +Endurance/Strength", "warn": "-Stress Resist"},
        ],
        "skill": [
            {"short": "Obdolbos 2", "desc": "All skills +20, weight +45%, 1800s",  "warn": "-stam, -HP regen"},
            {"short": "3-(b-TG)",   "desc": "+Attention/Perception/Strength 240s",  "warn": "tremors after"},
            {"short": "SJ12",       "desc": "+Perception 600s, body cool",          "warn": "overheats at end"},
            {"short": "2A2-(b-TG)", "desc": "+Attention/Perception, weight +15%, 900s", "warn": "-hydration"},
            {"short": "Trimadol",   "desc": "Broad skill buff + stam, 180s",        "warn": "-energy/-hydration"},
        ],
        "special": [
            {"short": "SJ9",    "desc": "Cools body -7°, 300s — hot map survival", "warn": "HP drain + tremors"},
            {"short": "SJ12",   "desc": "Cools body -4°, +Perception, 600s",       "warn": "rebound heat after"},
            {"short": "M.U.L.E.", "desc": "Weight limit +50% for 900s",            "warn": "-HP regen"},
            {"short": "Obdolbos","desc": "25% chance: all buffs + all debuffs",    "warn": "may kill you"},
        ],
        "risky": [
            {"short": "Obdolbos", "desc": "25% chance everything fires at once",  "warn": "may cause -600 HP"},
            {"short": "PNB",      "desc": "Fast HP/Strength burst then hard crash", "warn": "-Health/-Vitality 180s"},
            {"short": "SJ9",      "desc": "-HP regen whole duration, tremors",    "warn": "don't use while injured"},
            {"short": "Propital", "desc": "Tremors + tunnel vision at 270s delay", "warn": "plan ahead"},
        ],
    }

    return render_template("meds.html",
                           injectors=injectors,
                           skill_rows=skill_rows,
                           situations=situations)


@app.route("/barters")
def barters():
    import requests as _req

    API_URL = "https://api.tarkov.dev/graphql"
    query = """
    {
      barters(lang: en) {
        id
        trader { name }
        level
        taskUnlock { name }
        requiredItems {
          item { id name shortName iconLink wikiLink }
          count
        }
        rewardItems {
          item { id name shortName iconLink wikiLink }
          count
        }
      }
    }
    """
    try:
        resp = _req.post(API_URL, json={"query": query}, timeout=15)
        data = resp.json()
        raw_barters = data.get("data", {}).get("barters", [])
    except Exception:
        raw_barters = []

    barter_list = []
    for b in raw_barters:
        reward_items = b.get("rewardItems", [])
        required_items = b.get("requiredItems", [])
        if not reward_items or not required_items:
            continue

        # Use first reward item as the "output" item
        reward = reward_items[0]
        reward_item = reward.get("item") or {}
        reward_count = reward.get("count", 1)

        required = []
        for ri in required_items:
            item = ri.get("item") or {}
            required.append({
                "id": item.get("id", ""),
                "name": item.get("name", "Unknown"),
                "short": item.get("shortName", ""),
                "icon": item.get("iconLink"),
                "count": ri.get("count", 1),
            })

        task_unlock = b.get("taskUnlock")
        barter_list.append({
            "id": b.get("id", ""),
            "trader": (b.get("trader") or {}).get("name", "Unknown"),
            "level": b.get("level", 1),
            "task_unlock": task_unlock.get("name") if task_unlock else None,
            "reward_name": reward_item.get("name", "Unknown"),
            "reward_short": reward_item.get("shortName", ""),
            "reward_icon": reward_item.get("iconLink"),
            "reward_wiki": reward_item.get("wikiLink"),
            "reward_count": reward_count,
            "required": required,
        })

    barter_list.sort(key=lambda b: (b["trader"], b["level"], b["reward_name"]))
    return render_template("barters.html", barters=barter_list)


if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5000, debug=True)
