import streamlit as st
import pandas as pd
import numpy as np
import plotly.express as px

# =========================
# Config
# =========================
st.set_page_config(page_title="Dashboard Cervecera", page_icon="🍺", layout="wide")

# =========================
# Mock data base (ventas/costos)
# =========================
@st.cache_data
def make_mock_data(n_rows: int = 1000, seed: int = 42) -> pd.DataFrame:
    rng = np.random.default_rng(seed)

    end = pd.Timestamp.today().normalize()
    start = end - pd.Timedelta(days=364)
    fechas = pd.date_range(start, end, freq="D")

    fabricas = np.array(["Planta Norte", "Planta Sur"])
    tipos_cliente = np.array(["Independiente", "Eventos", "Cerveceras", "Distribuidores"])
    productos = np.array([
        "Lager", "IPA", "Stout", "Pilsner", "Porter",
        "Wheat", "Amber Ale", "Pale Ale", "Sour", "Hazy IPA"
    ])

    first_names = ["Casa", "Grupo", "Comercial", "Bodega", "Restaurante", "Bar", "Eventos", "Distribuidora", "Market", "Centro"]
    last_names = ["Del Mar", "Norte", "Pacífico", "Sierra", "Sol", "Malecón", "Olivo", "Gran Reserva", "Aurora", "Vértice"]
    clientes = np.array([f"{rng.choice(first_names)} {rng.choice(last_names)} {i:02d}" for i in range(1, 61)])

    Fecha = rng.choice(fechas, size=n_rows, replace=True)
    Fabrica = rng.choice(fabricas, size=n_rows, replace=True, p=[0.55, 0.45])
    Tipo_Cliente = rng.choice(tipos_cliente, size=n_rows, replace=True, p=[0.45, 0.15, 0.15, 0.25])
    Cliente = rng.choice(clientes, size=n_rows, replace=True)

    base_prod = rng.choice(productos, size=n_rows, replace=True)
    for i in range(n_rows):
        if Tipo_Cliente[i] == "Distribuidores":
            base_prod[i] = rng.choice(["Lager", "IPA", "Pilsner", "Pale Ale"], p=[0.35, 0.30, 0.20, 0.15])
        elif Tipo_Cliente[i] == "Eventos":
            base_prod[i] = rng.choice(["Hazy IPA", "IPA", "Sour", "Wheat"], p=[0.35, 0.30, 0.20, 0.15])
    Producto = base_prod

    base_sales = np.where(
        Tipo_Cliente == "Distribuidores", rng.normal(18000, 5000, n_rows),
        np.where(Tipo_Cliente == "Cerveceras", rng.normal(14000, 4500, n_rows),
                 np.where(Tipo_Cliente == "Eventos", rng.normal(12000, 4000, n_rows),
                          rng.normal(8000, 3000, n_rows)))
    )
    base_sales = np.clip(base_sales, 1200, None)

    premium_factor_map = {
        "Lager": 1.00, "Pilsner": 1.02, "Pale Ale": 1.05, "Amber Ale": 1.06,
        "Wheat": 1.04, "IPA": 1.10, "Hazy IPA": 1.14, "Stout": 1.08,
        "Porter": 1.07, "Sour": 1.12
    }
    premium_factor = np.array([premium_factor_map[p] for p in Producto])

    Ventas_Brutas = base_sales * premium_factor * rng.uniform(0.85, 1.15, n_rows)
    Ventas_Brutas = np.round(np.clip(Ventas_Brutas, 0, None), 2)

    cost_ratio = rng.uniform(0.55, 0.82, n_rows)
    Costo_Produccion = Ventas_Brutas * cost_ratio * rng.uniform(0.95, 1.05, n_rows)
    Costo_Produccion = np.minimum(Costo_Produccion, Ventas_Brutas * 0.95)
    Costo_Produccion = np.round(np.clip(Costo_Produccion, 0, None), 2)

    df = pd.DataFrame({
        "Fecha": pd.to_datetime(Fecha),
        "Fábrica": Fabrica,
        "Cliente": Cliente,
        "Tipo_Cliente": Tipo_Cliente,
        "Producto": Producto,
        "Ventas_Brutas": Ventas_Brutas,
        "Costo_Produccion": Costo_Produccion,
    }).sort_values("Fecha")

    return df


# =========================
# Mock inventario (movimientos)
#   - Producción (Entrada)
#   - Ventas (Salida)
# =========================
@st.cache_data
def make_inventory_from_sales(df_sales: pd.DataFrame, seed: int = 123) -> pd.DataFrame:
    """
    Genera movimientos diarios por (Fecha, Fábrica, Producto):
      Produccion_L = entradas
      Ventas_L     = salidas
      Stock_L      = stock acumulado
    """
    rng = np.random.default_rng(seed)

    # Ventas en "litros" (aprox): suponemos un precio por litro variable por producto
    price_per_l_map = {
        "Lager": 55, "Pilsner": 58, "Pale Ale": 62, "Amber Ale": 64,
        "Wheat": 60, "IPA": 70, "Hazy IPA": 75, "Stout": 68,
        "Porter": 66, "Sour": 72
    }

    tmp = df_sales.copy()
    tmp["Precio_L"] = tmp["Producto"].map(price_per_l_map).astype(float)

    # Litros vendidos por registro
    litros = (tmp["Ventas_Brutas"] / tmp["Precio_L"]) * rng.uniform(0.85, 1.20, len(tmp))
    tmp["Ventas_L"] = np.round(np.clip(litros, 0, None), 2)

    # Agregar a nivel diario por fábrica/producto
    daily_sales = (
        tmp.groupby(["Fecha", "Fábrica", "Producto"], as_index=False)["Ventas_L"]
        .sum()
        .sort_values(["Fábrica", "Producto", "Fecha"])
    )

    # Producción diaria: ventas del día + “buffer” (para crecer inventario)
    # y un poco de aleatoriedad; algunos días pueden producir menos.
    daily_sales["Produccion_L"] = (
        daily_sales["Ventas_L"] * rng.uniform(1.05, 1.45, len(daily_sales))
        + rng.normal(20, 15, len(daily_sales))  # base mínima
    )
    daily_sales["Produccion_L"] = np.round(np.clip(daily_sales["Produccion_L"], 0, None), 2)

    # Stock inicial por fábrica/producto
    stock_init = (
        daily_sales.groupby(["Fábrica", "Producto"])["Ventas_L"]
        .transform("mean")
        * rng.uniform(3, 8, len(daily_sales))
    )
    # Para que sea constante dentro del grupo, generamos un init real por grupo:
    init_map = {}
    for (fab, prod), g in daily_sales.groupby(["Fábrica", "Producto"]):
        init_map[(fab, prod)] = float(rng.uniform(200, 1200))
    daily_sales["Stock_Inicial_L"] = daily_sales.apply(lambda r: init_map[(r["Fábrica"], r["Producto"])], axis=1)

    # Stock acumulado: init + cumsum(produccion - ventas)
    daily_sales["Delta_L"] = daily_sales["Produccion_L"] - daily_sales["Ventas_L"]
    daily_sales["Stock_L"] = (
        daily_sales.groupby(["Fábrica", "Producto"])["Delta_L"].cumsum()
        + daily_sales["Stock_Inicial_L"]
    )
    daily_sales["Stock_L"] = np.round(np.clip(daily_sales["Stock_L"], 0, None), 2)

    return daily_sales[["Fecha", "Fábrica", "Producto", "Produccion_L", "Ventas_L", "Stock_L"]]


df = make_mock_data()
inv = make_inventory_from_sales(df)

# =========================
# Sidebar - Filtros
# =========================
st.sidebar.header("Filtros")

fab_options = sorted(df["Fábrica"].unique().tolist())
view_mode = st.sidebar.radio(
    "Ver por fábrica",
    options=["Total", *fab_options],
    index=0
)

# Selector mes/año
df["Año"] = df["Fecha"].dt.year
df["Mes"] = df["Fecha"].dt.month

years = sorted(df["Año"].unique().tolist())
year_sel = st.sidebar.selectbox("Año", options=years, index=len(years) - 1)

months_in_year = sorted(df.loc[df["Año"] == year_sel, "Mes"].unique().tolist())
month_map = {m: pd.Timestamp(year=year_sel, month=m, day=1).strftime("%B") for m in months_in_year}
month_label_sel = st.sidebar.selectbox("Mes", options=[month_map[m] for m in months_in_year], index=len(months_in_year) - 1)
month_sel = [m for m, lab in month_map.items() if lab == month_label_sel][0]

# =========================
# Aplicar filtros
# =========================
df_f = df.copy()
inv_f = inv.copy()

if view_mode != "Total":
    df_f = df_f[df_f["Fábrica"] == view_mode]
    inv_f = inv_f[inv_f["Fábrica"] == view_mode]

mes_ini = pd.Timestamp(year=year_sel, month=month_sel, day=1)
mes_fin = mes_ini + pd.offsets.MonthBegin(1)

df_m = df_f[(df_f["Fecha"] >= mes_ini) & (df_f["Fecha"] < mes_fin)].copy()
inv_m = inv_f[(inv_f["Fecha"] >= mes_ini) & (inv_f["Fecha"] < mes_fin)].copy()

# Stock "al cierre del mes" (último día disponible del mes por fábrica/producto)
stock_cierre_mes = (
    inv_m.sort_values("Fecha")
    .groupby(["Fábrica", "Producto"], as_index=False)
    .tail(1)
)

# Stock "actual" (último día disponible global por fábrica/producto)
stock_actual = (
    inv_f.sort_values("Fecha")
    .groupby(["Fábrica", "Producto"], as_index=False)
    .tail(1)
)

# Si vista Total, consolidamos sumando stock por producto
def consolidate_stock(df_stock: pd.DataFrame) -> pd.DataFrame:
    if view_mode == "Total":
        return df_stock.groupby("Producto", as_index=False)["Stock_L"].sum()
    return df_stock.copy()

stock_cierre_view = consolidate_stock(stock_cierre_mes)
stock_actual_view = consolidate_stock(stock_actual)

# =========================
# Header
# =========================
st.title("🍺 Dashboard Operativo & Financiero – Cervecera")
st.caption(f"Vista: **{view_mode}** | Periodo: **{mes_ini.strftime('%B %Y')}**")

# =========================
# KPIs
# =========================
ventas_total = float(df_m["Ventas_Brutas"].sum())
costos_total = float(df_m["Costo_Produccion"].sum())
margen_abs = ventas_total - costos_total
margen_pct = (margen_abs / ventas_total) if ventas_total > 0 else 0.0

k1, k2, k3, k4 = st.columns(4)
k1.metric("Ventas Totales ($)", f"${ventas_total:,.0f}")
k2.metric("Costos Totales ($)", f"${costos_total:,.0f}")
k3.metric("Margen ($)", f"${margen_abs:,.0f}")
k4.metric("Margen (%)", f"{margen_pct:.1%}")

st.divider()

# =========================
# Inventario (Almacén)
# =========================
st.subheader("📦 Inventario (Almacén) por cerveza")

inv_k1, inv_k2 = st.columns(2)
stock_cierre_total = float(stock_cierre_view["Stock_L"].sum()) if len(stock_cierre_view) else 0.0
stock_actual_total = float(stock_actual_view["Stock_L"].sum()) if len(stock_actual_view) else 0.0

inv_k1.metric("Stock al cierre del mes (L)", f"{stock_cierre_total:,.0f}")
inv_k2.metric("Stock actual (L)", f"{stock_actual_total:,.0f}")

inv_cols = st.columns((1.2, 1))
# Gráfico stock cierre por producto
inv_bar = stock_cierre_view.sort_values("Stock_L", ascending=False).copy()
inv_bar_fig = px.bar(
    inv_bar,
    x="Stock_L",
    y="Producto",
    orientation="h",
    title="Stock al cierre del mes por cerveza (L)",
    labels={"Stock_L": "Stock (L)", "Producto": "Cerveza"},
)
inv_bar_fig.update_layout(height=420, margin=dict(l=10, r=10, t=60, b=10))
inv_cols[0].plotly_chart(inv_bar_fig, use_container_width=True)

# Tabla inventario: cierre vs actual
stock_table = (
    stock_cierre_view.merge(
        stock_actual_view,
        on="Producto",
        how="outer",
        suffixes=("_CierreMes", "_Actual")
    )
    .fillna(0)
)

stock_table = stock_table.rename(columns={
    "Stock_L_CierreMes": "Stock_CierreMes_L",
    "Stock_L_Actual": "Stock_Actual_L",
}).sort_values("Stock_CierreMes_L", ascending=False)

inv_cols[1].dataframe(
    stock_table,
    use_container_width=True,
    hide_index=True,
    column_config={
        "Stock_CierreMes_L": st.column_config.NumberColumn("Stock cierre mes (L)", format="%0.0f"),
        "Stock_Actual_L": st.column_config.NumberColumn("Stock actual (L)", format="%0.0f"),
    },
)

st.divider()

# =========================
# Mix / Tipo_Cliente
# =========================
c1, c2 = st.columns((1.2, 1))

mix = (
    df_m.groupby("Producto", as_index=False)["Ventas_Brutas"]
    .sum()
    .sort_values("Ventas_Brutas", ascending=False)
    .head(10)
)
mix_fig = px.bar(
    mix.sort_values("Ventas_Brutas"),
    x="Ventas_Brutas",
    y="Producto",
    orientation="h",
    title="Top 10 Cervezas más vendidas ($)",
    labels={"Ventas_Brutas": "Ventas ($)", "Producto": "Cerveza"},
)
mix_fig.update_layout(height=420, margin=dict(l=10, r=10, t=60, b=10))
c1.plotly_chart(mix_fig, use_container_width=True)

tipo = (
    df_m.groupby("Tipo_Cliente", as_index=False)["Ventas_Brutas"]
    .sum()
    .sort_values("Ventas_Brutas", ascending=False)
)

chart_mode = c2.radio("Visualización", ["Pie", "Treemap"], horizontal=True)
if chart_mode == "Pie":
    tipo_fig = px.pie(tipo, values="Ventas_Brutas", names="Tipo_Cliente",
                      title="Distribución de ingresos por Tipo_Cliente", hole=0.35)
else:
    tipo_fig = px.treemap(tipo, path=["Tipo_Cliente"], values="Ventas_Brutas",
                          title="Distribución de ingresos por Tipo_Cliente")

tipo_fig.update_layout(height=420, margin=dict(l=10, r=10, t=60, b=10))
c2.plotly_chart(tipo_fig, use_container_width=True)

st.divider()

# =========================
# Ranking clientes + tendencia
# =========================
c3, c4 = st.columns((1.1, 1.3))

top_clientes = (
    df_m.groupby(["Cliente", "Tipo_Cliente"], as_index=False)["Ventas_Brutas"]
    .sum()
    .sort_values("Ventas_Brutas", ascending=False)
    .head(10)
)
c3.subheader("🏆 Top 10 Clientes del mes (por ingreso)")
c3.dataframe(
    top_clientes,
    use_container_width=True,
    hide_index=True,
    column_config={"Ventas_Brutas": st.column_config.NumberColumn("Ventas ($)", format="$%0.0f")},
)

daily = (
    df_m.groupby("Fecha", as_index=False)[["Ventas_Brutas", "Costo_Produccion"]]
    .sum()
    .sort_values("Fecha")
)
line_fig = px.line(
    daily,
    x="Fecha",
    y=["Ventas_Brutas", "Costo_Produccion"],
    title="Tendencia diaria: Ingresos vs Costos (mes seleccionado)",
    labels={"value": "Monto ($)", "variable": "Métrica"},
)
line_fig.update_layout(height=420, margin=dict(l=10, r=10, t=60, b=10))
c4.plotly_chart(line_fig, use_container_width=True)

with st.expander("Ver datos filtrados (debug)"):
    st.write("Ventas/Costos (mes):")
    st.dataframe(df_m.sort_values("Fecha"), use_container_width=True, hide_index=True)
    st.write("Inventario movimientos (mes):")
    st.dataframe(inv_m.sort_values(["Fecha", "Fábrica", "Producto"]), use_container_width=True, hide_index=True)
