import os
import json
import streamlit as st
import pandas as pd
from PIL import Image
from pathlib import Path
import time
import folium
from streamlit_folium import st_folium
import math
import base64
from io import BytesIO

# --- CACHE IMMAGINE ---
@st.cache_data
def get_image_base64(img_path):
    img = Image.open(img_path).convert("RGBA")
    w, h = img.size
    buffered = BytesIO()
    img.save(buffered, format="PNG")
    img_str = base64.b64encode(buffered.getvalue()).decode("ascii")
    return f"data:image/png;base64,{img_str}", w, h

# --- FUNZIONE SALVATAGGIO FISICO ---
def force_save_json(path, data):
    try:
        with open(path, "w", encoding='utf-8') as f:
            json.dump(data, f, indent=4)
        return True
    except Exception as e:
        st.error(f"Errore scrittura disco: {e}")
        return False

def show_mapper(cfg):
    # Configurazione percorsi
    MAPS_DIR = Path(cfg['maps']['map_dir'])
    TRAIN_BASE = Path("/data/train")
    JOBS_BASE = Path(cfg['collect_train']['jobs_dir'])

    # Riferimenti directory per Addestramento e Test
    SAMPLES_DIR = TRAIN_BASE / "samples"
    TEST_SAMPLES_DIR = TRAIN_BASE / "testsamples"
    PENDING_DIR = JOBS_BASE / "pending"
    ERROR_DIR = JOBS_BASE / "error"
    TEST_PENDING_DIR = TRAIN_BASE / "testjobs/pending"
    TEST_ERROR_DIR = TRAIN_BASE / "testjobs/error"

    # File configurazione
    BEACONS_FILE = cfg.get('paths', {}).get('beacons_csv', "/data/config/beacons.csv")
    GROUPS_FILE = "/data/config/beacongroup.csv"
    CSV_DELIM = cfg.get('paths', {}).get('csv_delimiter', ';')

    # Creazione directory se mancano
    for p in [MAPS_DIR, SAMPLES_DIR, TEST_SAMPLES_DIR, PENDING_DIR, ERROR_DIR, TEST_PENDING_DIR, TEST_ERROR_DIR]:
        p.mkdir(parents=True, exist_ok=True)

    # --- 1. GESTIONE UPLOAD ---
    maps = sorted([f.replace(cfg['maps']['floor_prefix'], "").split('.')[0] 
                   for f in os.listdir(MAPS_DIR) if f.startswith(cfg['maps']['floor_prefix'])])

    with st.expander("📂 Carica Nuova Planimetria", expanded=not maps):
        c_up1, c_up2 = st.columns([1, 2])
        new_f_id = c_up1.text_input("ID Piano (es. 0, -1):", key="new_f_id_v21")
        up_file = c_up2.file_uploader("Immagine (PNG/JPG):", type=['png', 'jpg', 'jpeg'], key="up_v21")
        if st.button("🚀 SALVA PIANO", use_container_width=True):
            if new_f_id and up_file:
                ext = Path(up_file.name).suffix
                img_save_path = MAPS_DIR / f"{cfg['maps']['floor_prefix']}{new_f_id}{ext}"
                with open(img_save_path, "wb") as f:
                    f.write(up_file.getbuffer())
                
                m_path = MAPS_DIR / f"{cfg['maps']['meta_prefix']}{new_f_id}.json"
                force_save_json(m_path, {"pixel_ratio": 1.0, "calibrated": False, "origin": [0, 0], "grid_size": 50})
                st.session_state.current_floor = new_f_id
                st.rerun()

    if not maps: return

    # --- 2. RIGA STATO ---
    c_p, c_s, c_o = st.columns([2.5, 2, 2])
    with c_p:
        s1, s2 = st.columns([1, 1.2])
        s1.markdown("<p style='padding-top:35px; font-weight:bold;'>Piano Attivo:</p>", unsafe_allow_html=True)
        def_idx = maps.index(st.session_state.current_floor) if st.session_state.get("current_floor") in maps else 0
        floor_id = s2.selectbox("", maps, index=def_idx, key="f_v21", label_visibility="collapsed")
        if st.session_state.get("prev_floor") != floor_id:
            st.session_state.cal_points = []
            st.session_state.temp_lat, st.session_state.temp_lng = None, None
            st.session_state.prev_floor = floor_id

    meta_path = MAPS_DIR / f"{cfg['maps']['meta_prefix']}{floor_id}.json"
    if meta_path.exists():
        with open(meta_path, "r") as f: meta = json.load(f)
    else:
        meta = {"pixel_ratio": 1.0, "calibrated": False, "origin": [0, 0]}

    with c_s: st.info(f"📏 Scala: {'✅' if meta['calibrated'] else '❌'}\n({meta['pixel_ratio']:.4f} px/cm)")
    with c_o: st.info(f"🎯 Origine: {'✅' if meta['origin'] != [0,0] else '❌'}\n(X:{meta['origin'][0]}, Y:{meta['origin'][1]})")

    # --- 3. IMPOSTAZIONI GLOBALI ---
    st.markdown("---")
    g1, g2, g3, g4 = st.columns([1.2, 1.5, 1.2, 2])
    with g1: show_grid = st.toggle("Griglia", value=False, key="grid_v21")
    with g2: grid_cm = st.select_slider("Passo (cm):", options=[25, 50, 100, 200], value=50, key="step_v21")
    with g3: snap_on = st.toggle("Stay Grid", value=False, key="snap_v21")
    with g4: m_size = st.slider("Dimensione Marker:", 5, 20, 8, key="msize_v21")
    st.markdown("---")

    # --- 4. TOOLSET ---
    t1, t2, t3 = st.columns(3)
    if t1.button("📏 CALIBRA", use_container_width=True): st.session_state.map_tool = "Calibra"; st.session_state.cal_points = []
    if t2.button("🎯 SET ORIGINE", use_container_width=True): st.session_state.map_tool = "Origine"
    if t3.button("📡 RILEVA", use_container_width=True): st.session_state.map_tool = "Rileva"
    tool = st.session_state.get('map_tool', 'Rileva')

    # --- 5. MAPPA ---
    col_map, col_ui = st.columns([3, 1.2])
    with col_map:
        img_p = next((MAPS_DIR / f"{cfg['maps']['floor_prefix']}{floor_id}{e}" for e in ['.png','.jpg','.jpeg'] if (MAPS_DIR / f"{cfg['maps']['floor_prefix']}{floor_id}{e}").exists()))
        img_data, w, h = get_image_base64(img_p)
        bounds = [[0, 0], [h, w]]
        m = folium.Map(location=[h/2, w/2], crs="Simple", tiles=None, attribution_control=False)
        m.fit_bounds(bounds)
        m.options.update({"minZoom": -6, "maxZoom": 6, "zoomSnap": 0.25, "maxBounds": bounds, "maxBoundsViscosity": 1.0})
        folium.raster_layers.ImageOverlay(image=img_data, bounds=bounds, interactive=True).add_to(m)

        if meta["origin"] != [0, 0]:
            ox, oy = meta["origin"]
            folium.CircleMarker(location=[oy, ox], radius=6, color="black", fill=True, zindex=1000).add_to(m)

        if show_grid and meta["calibrated"] and meta["origin"] != [0, 0]:
            px_step = grid_cm * meta["pixel_ratio"]
            ox, oy = meta["origin"]
            for x in sorted(list(range(int(ox), w, int(px_step))) + list(range(int(ox), 0, -int(px_step)))):
                folium.PolyLine([[0, x], [h, x]], color="blue", weight=1, opacity=0.1).add_to(m)
            for y in sorted(list(range(int(oy), h, int(px_step))) + list(range(int(oy), 0, -int(px_step)))):
                folium.PolyLine([[y, 0], [y, w]], color="blue", weight=1, opacity=0.1).add_to(m)

        # --- DISEGNO PUNTI STORICI ---
        def draw_historical_samples(directory, color, shape="circle", is_test=False):
            if not meta["calibrated"] or meta["origin"] == [0,0]: return
            for f in Path(directory).glob("*.csv"):
                try:
                    parts = f.stem.split('_')
                    if len(parts) < 4: continue
                    fz = parts[-3]
                    fx = float(parts[-2].replace(',', '.'))
                    fy = float(parts[-1].replace(',', '.'))
                    if str(fz) == str(floor_id):
                        px_x = (fx * meta["pixel_ratio"]) + meta["origin"][0]
                        px_y = meta["origin"][1] - (fy * meta["pixel_ratio"])
                        dash = "5, 5" if is_test else None
                        weight = 3 if is_test else 1
                        folium.CircleMarker(
                            location=[px_y, px_x], radius=m_size, color=color,
                            weight=weight, dash_array=dash, fill=True, fill_opacity=0.7
                        ).add_to(m)
                except: continue

        draw_historical_samples(SAMPLES_DIR, "green", "circle", is_test=False)
        draw_historical_samples(TEST_SAMPLES_DIR, "#00FFFF", "circle", is_test=True)

        def draw_jobs(directory, color, shape="diamond", is_test=False):
            if not meta["calibrated"] or meta["origin"] == [0,0]: return
            for f in Path(directory).glob("*.csv"):
                try:
                    df = pd.read_csv(f, sep=";")
                    df.columns = [c.lower() for c in df.columns]
                    row = df.iloc[0]
                    z_val = str(row.get('z', row.get('floor')))
                    if z_val == str(floor_id):
                        px_x = (row['x'] * meta["pixel_ratio"]) + meta["origin"][0]
                        px_y = meta["origin"][1] - (row['y'] * meta["pixel_ratio"])
                        dash = "3, 3" if is_test else None
                        folium.RegularPolygonMarker(
                            location=[px_y, px_x], number_of_sides=4,
                            rotation=45 if shape=="diamond" else 0,
                            radius=m_size, color=color, weight=2,
                            dash_array=dash, fill=True
                        ).add_to(m)
                except: continue

        draw_jobs(PENDING_DIR, "orange", "diamond", False)
        draw_jobs(TEST_PENDING_DIR, "#f1c40f", "diamond", True)
        draw_jobs(ERROR_DIR, "red", "square", False)
        draw_jobs(TEST_ERROR_DIR, "#e74c3c", "square", True)

        if tool == "Calibra" and st.session_state.get("cal_points"):
            for p in st.session_state.cal_points: folium.CircleMarker(location=p, radius=6, color="red", fill=True).add_to(m)
            if len(st.session_state.cal_points) == 2: folium.PolyLine(st.session_state.cal_points, color="red", weight=3).add_to(m)
        elif st.session_state.get("temp_lat") is not None:
            folium.CircleMarker(location=[st.session_state.temp_lat, st.session_state.temp_lng], radius=m_size+2, color="blue", fill=True, zindex=500).add_to(m)

        out = st_folium(m, height=600, width=None, key=f"map_v24_{floor_id}_{tool}")
        click = out.get("last_clicked")
        if click:
            clat, clng = click["lat"], click["lng"]
            if tool == "Calibra":
                if len(st.session_state.get("cal_points", [])) < 2:
                    st.session_state.cal_points.append([clat, clng]); st.rerun()
            else:
                if tool == "Rileva" and snap_on and meta["calibrated"]:
                    px_step = grid_cm * meta["pixel_ratio"]
                    st.session_state.temp_lng = meta["origin"][0] + round((clng - meta["origin"][0]) / px_step) * px_step
                    st.session_state.temp_lat = meta["origin"][1] + round((clat - meta["origin"][1]) / px_step) * px_step
                else:
                    st.session_state.temp_lat, st.session_state.temp_lng = clat, clng
                st.rerun()

    # --- 6. LOGICA UI ---
    with col_ui:
        st.write(f"### Tool: **{tool}**")

        if tool == "Calibra":
            pts = st.session_state.get("cal_points", [])
            if len(pts) == 2:
                dist_cm = st.number_input("Distanza reale (cm):", value=100.0)
                if st.button("📏 SALVA SCALA", use_container_width=True, type="primary"):
                    px_d = math.sqrt((pts[1][1]-pts[0][1])**2 + (pts[1][0]-pts[0][0])**2)
                    meta.update({"pixel_ratio": px_d / dist_cm, "calibrated": True})
                    if force_save_json(meta_path, meta): st.session_state.cal_points = []; st.rerun()
        
        elif st.session_state.get("temp_lat") is not None:
            px_x, px_y = st.session_state.temp_lng, st.session_state.temp_lat
            if tool == "Origine":
                st.metric("X (px)", int(px_x)); st.metric("Y (px)", int(px_y))
                if st.button("💾 SALVA ORIGINE", use_container_width=True, type="primary"):
                    meta["origin"] = [int(px_x), int(px_y)]
                    if force_save_json(meta_path, meta): st.rerun()
            
            elif tool == "Rileva" and meta["calibrated"]:
                rx = (px_x - meta["origin"][0]) / meta["pixel_ratio"]
                ry = (meta["origin"][1] - px_y) / meta["pixel_ratio"]
                sx, sy = int(round(rx)), int(round(ry))
                
                # --- OTTIMIZZAZIONE SPAZIO: X e Y SU STESSA RIGA ---
                c_c1, c_c2 = st.columns(2)
                c_c1.metric("X (cm)", sx)
                c_c2.metric("Y (cm)", sy)

                # Divisore compresso con margine ridotto
                st.markdown("<hr style='margin: 0.5em 0;'>", unsafe_allow_html=True)

                job_mode = st.radio("Scopo Rilevamento:", ["Addestramento", "Test"], horizontal=True, key="job_mode_v24")

                # --- STILE CSS PER OTTIMIZZAZIONE TABLET (Tapping) ---
                st.markdown("""
                <style>
                [data-testid="stRadio"] > div {
                    gap: 1.0rem;
                }
                [data-testid="stRadio"] label {
                    font-size: 19px !important;
                    padding: 5px 0px;
                }
                [data-testid="stRadio"] div[role="radiogroup"] > div {
                    background-color: #f0f2f6;
                    padding: 4px 12px;
                    border-radius: 8px;
                    margin-bottom: 3px;
                }
                /* Riduzione whitespace globale colonna UI */
                .element-container { margin-bottom: 0.3rem !important; }
                </style>
                """, unsafe_allow_html=True)

                if Path(GROUPS_FILE).exists():
                    g_df = pd.read_csv(Path(GROUPS_FILE), sep=CSV_DELIM)
                    list_groups = [f"Gruppo: {n}" for n in g_df['BeaconGroupName']]
                else:
                    list_groups = []

                if not list_groups:
                    st.warning("⚠️ Configura i Gruppi.")
                else:
                    # Selezione target via radio per evitare tastiera
                    sel_target = st.radio("🎯 Selezione Target (Solo Gruppi):", list_groups, key="sel_target_v24_touch")

                    if st.button("🚀 REGISTRA JOB", use_container_width=True, type="primary"):
                        sub_dir = "jobs" if job_mode == "Addestramento" else "testjobs"
                        current_pending = Path(f"/data/train/{sub_dir}/pending")
                        current_pending.mkdir(parents=True, exist_ok=True)

                        b_df = pd.read_csv(Path(BEACONS_FILE), sep=CSV_DELIM) if Path(BEACONS_FILE).exists() else pd.DataFrame(columns=['BeaconName','MAC'])
                        beacon_name_map = {row['MAC']: row['BeaconName'] for _, row in b_df.iterrows()}

                        job_rows = []
                        g_name = sel_target.replace("Gruppo: ", "")
                        g_df = pd.read_csv(Path(GROUPS_FILE), sep=CSV_DELIM)
                        macs_str = g_df[g_df['BeaconGroupName'] == g_name]['GroupMAC'].iloc[0]
                        mac_list = [m.strip() for m in macs_str.split(',')]
                        job_filename = f"{g_name}_{floor_id}_{sx}_{sy}.csv"
                        
                        for m in mac_list:
                            b_name = beacon_name_map.get(m, g_name)
                            pos_id = f"{b_name}_{floor_id}_{sx}_{sy}"
                            job_rows.append({"Position": pos_id, "Floor": floor_id, "RoomName": b_name, "X": sx, "Y": sy, "Z": floor_id, "BeaconName": b_name, "MAC": m})

                        header = ["Position", "Floor", "RoomName", "X", "Y", "Z", "BeaconName", "MAC"]
                        pd.DataFrame(job_rows)[header].to_csv(current_pending / job_filename, sep=CSV_DELIM, index=False)
                        st.success(f"Job registrato!")
                        time.sleep(0.5)
                        st.rerun()

    # Pulizia errori nel menu laterale
    err_files = list(ERROR_DIR.glob("*.csv")) + list(TEST_ERROR_DIR.glob("*.csv"))
    if err_files and st.sidebar.button(f"🗑️ PULISCI ERRORI ({len(err_files)})"):
        for f in err_files: os.remove(f)
        st.rerun()
