import re

import customtkinter as ctk
from tkinter import messagebox, filedialog

PORCENTAJES = [30, 20, 10, 5, 1]
LIMITE = 28571.43  # US$28,571.43 (¢250,000) — exento hasta este monto (Ley del Impuesto sobre Transferencia de Bienes Raíces)
EXTRA = 3          # 3% SOLO sobre el excedente del límite

# ---- Presets con parámetros de Hacienda (El Salvador) ----
# nombre -> (límite $, % extra, modo, nota)
PRESETS = {
    "Bienes raíces (3% excedente)": (28571.43, 3.0, "Lo que se pasa",
     "Ley del Impuesto sobre Transferencia de Bienes Raíces: exento hasta $28,571.43; 3% SOLO sobre el excedente."),
    "IVA general (13%)": (0.0, 13.0, "Venta completa",
     "IVA general 13% sobre cada venta (Ley IVA — referencia)."),
    "Retención 1% gran contribuyente": (100.0, 1.0, "Venta completa",
     "Art. 162 Código Tributario: grandes contribuyentes retienen 1% del precio en operaciones ≥ $100."),
    "Anticipo tarjeta (2%)": (0.0, 2.0, "Venta completa",
     "Art. 162-A Código Tributario: anticipo 2% a cuenta del IVA en pagos con tarjeta de crédito/débito."),
    "Personalizado": (None, None, None,
     "Valores manuales: edita límite, % extra y modo a tu gusto."),
}

# ---- Colores (cambia ACENTO si quieres otro color de realce) ----
BG = "#0B0B0B"
CARD = "#141414"
HUECO = "#0F0F0F"
LINEA = "#262626"
TEXTO = "#F5F5F5"
GRIS = "#8A8A8A"
ACENTO = "#3DDC97"
AVISO = "#FDB022"
MONO = "Consolas"
SANS = "Segoe UI"

ctk.set_appearance_mode("dark")


def dinero(n):
    return f"${n:,.2f}"


def numero(texto, defecto=0.0):
    try:
        return float(str(texto).replace("$", "").replace(",", "").strip())
    except ValueError:
        return defecto


def _monto(texto):
    """Devuelve el monto numérico de un campo, o None si no lo es."""
    s = str(texto).replace("$", "").replace('"', "").replace(" ", "").strip()
    if re.fullmatch(r"\d{1,3}(,\d{3})+(\.\d+)?", s):
        s = s.replace(",", "")
    if re.fullmatch(r"\d+(\.\d+)?", s):
        return float(s)
    return None


def venta_de_fila(linea):
    """De una línea de CSV/texto saca el monto de la venta (el último número de la línea)."""
    s = linea.strip()
    if not s:
        return None
    n = _monto(s)
    if n:
        return n
    for campo in reversed(re.split(r"[;,\t]", s)):
        n = _monto(campo)
        if n:
            return n
    return None


def mono(parent, texto, color=GRIS, size=11, bold=False):
    return ctk.CTkLabel(parent, text=texto, text_color=color,
                        font=ctk.CTkFont(family=MONO, size=size, weight="bold" if bold else "normal"))


def tarjeta(parent, num, titulo):
    f = ctk.CTkFrame(parent, fg_color=CARD, border_width=1, border_color=LINEA, corner_radius=16)
    f.grid_columnconfigure(0, weight=1)
    cab = ctk.CTkFrame(f, fg_color="transparent")
    cab.grid(row=0, column=0, sticky="ew", padx=18, pady=(16, 8))
    mono(cab, f"{num} /", ACENTO, 12, True).pack(side="left")
    ctk.CTkLabel(cab, text=titulo, text_color=TEXTO,
                 font=ctk.CTkFont(family=SANS, size=17, weight="bold")).pack(side="left", padx=6)
    return f


class App(ctk.CTk):
    def __init__(self):
        super().__init__()
        self.title("Calculadora de Impuestos")
        self.geometry("1100x780")
        self.minsize(1000, 720)
        self.configure(fg_color=BG)
        self.ventas = []

        self.grid_columnconfigure(0, weight=1)
        self.grid_rowconfigure(3, weight=1)

        self.crear_nav()
        self.crear_hero()
        self.crear_stats()

        cuerpo = ctk.CTkFrame(self, fg_color="transparent")
        cuerpo.grid(row=3, column=0, sticky="nsew", padx=28, pady=(0, 8))
        cuerpo.grid_columnconfigure(0, weight=4, uniform="c")
        cuerpo.grid_columnconfigure(1, weight=6, uniform="c")
        cuerpo.grid_rowconfigure(0, weight=1)
        self.crear_ventas(cuerpo)
        self.crear_derecha(cuerpo)

        mono(self, "* Presets con parámetros de Hacienda/DGII El Salvador. Cálculo de referencia — confirma con tu contador.",
             GRIS, 10).grid(row=4, column=0, pady=(2, 12))
        self.calcular()

    # ---------- Barra superior ----------
    def crear_nav(self):
        nav = ctk.CTkFrame(self, fg_color="transparent")
        nav.grid(row=0, column=0, sticky="ew", padx=28, pady=(14, 10))
        mono(nav, "◆  CALCULADORA", TEXTO, 14, True).pack(side="left")
        mono(nav, "●  EN VIVO", ACENTO, 11, True).pack(side="right")
        ctk.CTkFrame(self, height=1, fg_color=LINEA, corner_radius=0).grid(row=0, column=0, sticky="sew")

    # ---------- Encabezado grande ----------
    def crear_hero(self):
        h = ctk.CTkFrame(self, fg_color="transparent")
        h.grid(row=1, column=0, sticky="ew", padx=28, pady=(18, 12))
        mono(h, "[ DESGLOSE DE IMPUESTOS ]", GRIS, 11).pack(anchor="w")
        ctk.CTkLabel(h, text="Tus impuestos, al instante.", text_color=TEXTO, justify="left",
                     font=ctk.CTkFont(family=SANS, size=34, weight="bold")).pack(anchor="w", pady=(4, 0))
        ctk.CTkLabel(h, text="Elige un caso de uso, agrega o importa tus ventas en CSV, y mira cuánto es cada porcentaje.",
                     text_color=GRIS, font=ctk.CTkFont(family=SANS, size=14)).pack(anchor="w")

    # ---------- Franja de números grandes ----------
    def crear_stats(self):
        fila = ctk.CTkFrame(self, fg_color="transparent")
        fila.grid(row=2, column=0, sticky="ew", padx=28, pady=(4, 14))
        for c in range(3):
            fila.grid_columnconfigure(c, weight=1, uniform="s")
        self.s_total = self.stat(fila, 0, "TOTAL DE VENTAS", ACENTO)
        self.s_pasan = self.stat(fila, 1, "VENTAS QUE SE PASAN", TEXTO)
        self.s_extra = self.stat(fila, 2, "EXTRA A PAGAR", AVISO)

    def stat(self, fila, col, titulo, color):
        f = ctk.CTkFrame(fila, fg_color=CARD, border_width=1, border_color=LINEA, corner_radius=16)
        f.grid(row=0, column=col, sticky="ew", padx=(0 if col == 0 else 6, 0 if col == 2 else 6))
        lbl = ctk.CTkLabel(f, text="", text_color=color, font=ctk.CTkFont(family=SANS, size=30, weight="bold"))
        lbl.pack(anchor="w", padx=18, pady=(14, 0))
        mono(f, titulo, GRIS, 10).pack(anchor="w", padx=18, pady=(0, 14))
        return lbl

    # ---------- 01 Ventas ----------
    def crear_ventas(self, cuerpo):
        p = tarjeta(cuerpo, "01", "Ventas")
        p.grid(row=0, column=0, sticky="nsew", padx=(0, 8))
        p.grid_rowconfigure(2, weight=1)

        fila = ctk.CTkFrame(p, fg_color="transparent")
        fila.grid(row=1, column=0, sticky="ew", padx=18)
        fila.grid_columnconfigure(0, weight=1)
        self.monto = ctk.CTkEntry(fila, placeholder_text="Monto de la venta", height=42, corner_radius=21,
                                  fg_color=HUECO, border_color=LINEA, text_color=TEXTO,
                                  font=ctk.CTkFont(family=SANS, size=14))
        self.monto.grid(row=0, column=0, sticky="ew", padx=(0, 8))
        self.monto.bind("<Return>", lambda e: self.agregar())
        ctk.CTkButton(fila, text="Agregar →", width=100, height=42, corner_radius=21,
                      fg_color=TEXTO, text_color=BG, hover_color="#D4D4D4",
                      font=ctk.CTkFont(family=SANS, weight="bold"), command=self.agregar
                      ).grid(row=0, column=1)

        self.lista = ctk.CTkScrollableFrame(p, fg_color=HUECO, corner_radius=12)
        self.lista.grid(row=2, column=0, sticky="nsew", padx=18, pady=12)
        self.lista.grid_columnconfigure(0, weight=1)

        botones = ctk.CTkFrame(p, fg_color="transparent")
        botones.grid(row=3, column=0, sticky="ew", padx=18, pady=(0, 16))
        ctk.CTkButton(botones, text="📄 Importar CSV", height=38, fg_color="transparent", border_width=1,
                      border_color=LINEA, text_color=GRIS, hover_color="#1C1C1C", corner_radius=20,
                      command=self.importar_csv).pack(side="left", fill="x", expand=True, padx=(0, 8))
        ctk.CTkButton(botones, text="Limpiar todo", height=38, fg_color="transparent", border_width=1,
                      border_color=LINEA, text_color=GRIS, hover_color="#1C1C1C", corner_radius=20,
                      command=self.limpiar).pack(side="left", fill="x", expand=True)

    # ---------- 02 Ajustes y 03 Desglose ----------
    def crear_derecha(self, cuerpo):
        col = ctk.CTkFrame(cuerpo, fg_color="transparent")
        col.grid(row=0, column=1, sticky="nsew", padx=(8, 0))
        col.grid_columnconfigure(0, weight=1)
        col.grid_rowconfigure(1, weight=1)

        aj = tarjeta(col, "02", "Ajustes")
        aj.grid(row=0, column=0, sticky="ew")
        cont = ctk.CTkFrame(aj, fg_color="transparent")
        cont.grid(row=1, column=0, sticky="ew", padx=18, pady=(0, 16))

        def entrada(w, var, **kw):
            return ctk.CTkEntry(cont, textvariable=var, width=w, fg_color=HUECO, border_color=LINEA,
                                text_color=TEXTO, corner_radius=10, **kw)

        # Caso de uso (preset con parámetros de Hacienda)
        self.v_preset = ctk.StringVar(value="Bienes raíces (3% excedente)")
        mono(cont, "CASO DE USO").grid(row=0, column=0, sticky="w", pady=(0, 2))
        ctk.CTkOptionMenu(cont, values=list(PRESETS.keys()), variable=self.v_preset,
                          command=self.aplicar_preset, width=330, anchor="w",
                          fg_color=HUECO, button_color="#2A2A2A", button_hover_color="#333333",
                          text_color=TEXTO, corner_radius=10
                          ).grid(row=0, column=1, columnspan=3, sticky="w", padx=(10, 0))
        self.nota_preset = ctk.CTkLabel(cont, text=PRESETS["Bienes raíces (3% excedente)"][3], text_color=GRIS,
                                        justify="left", wraplength=520,
                                        font=ctk.CTkFont(family=SANS, size=12))
        self.nota_preset.grid(row=1, column=1, columnspan=3, sticky="w", padx=(10, 0), pady=(0, 6))

        self.v_limite = ctk.StringVar(value=str(LIMITE))
        self.v_extra = ctk.StringVar(value=str(EXTRA))
        mono(cont, "LÍMITE POR VENTA ($)").grid(row=2, column=0, sticky="w", pady=6)
        entrada(110, self.v_limite).grid(row=2, column=1, padx=(10, 24))
        mono(cont, "% EXTRA SI SE PASA").grid(row=2, column=2, sticky="w")
        entrada(70, self.v_extra).grid(row=2, column=3, padx=10)

        mono(cont, "PORCENTAJES").grid(row=3, column=0, sticky="w", pady=6)
        pc = ctk.CTkFrame(cont, fg_color="transparent")
        pc.grid(row=3, column=1, columnspan=4, sticky="w", padx=10)
        self.v_pcts = []
        for pct in PORCENTAJES:
            v = ctk.StringVar(value=str(pct))
            ctk.CTkEntry(pc, textvariable=v, width=52, justify="center", fg_color=HUECO,
                         border_color=LINEA, text_color=TEXTO, corner_radius=10).pack(side="left", padx=(0, 6))
            self.v_pcts.append(v)

        mono(cont, "EL EXTRA SE COBRA SOBRE").grid(row=4, column=0, sticky="w", pady=6)
        self.v_modo = ctk.StringVar(value="Lo que se pasa")
        ctk.CTkSegmentedButton(cont, values=["Lo que se pasa", "Venta completa"], variable=self.v_modo,
                               command=lambda *_: self.calcular(), fg_color=HUECO,
                               selected_color="#2A2A2A", selected_hover_color="#2A2A2A",
                               unselected_color=HUECO, unselected_hover_color="#1C1C1C",
                               text_color=TEXTO).grid(row=4, column=1, columnspan=4, sticky="w", padx=10)

        for v in [self.v_limite, self.v_extra] + self.v_pcts:
            v.trace_add("write", lambda *a: self.calcular())

        res = tarjeta(col, "03", "Desglose")
        res.grid(row=1, column=0, sticky="nsew", pady=(14, 0))
        self.tabla = ctk.CTkFrame(res, fg_color="transparent")
        self.tabla.grid(row=1, column=0, sticky="ew", padx=18)
        for c in range(3):
            self.tabla.grid_columnconfigure(c, weight=1)
        self.aviso = ctk.CTkLabel(res, text="", justify="left", wraplength=520,
                                  font=ctk.CTkFont(family=SANS, size=13, weight="bold"))
        self.aviso.grid(row=2, column=0, sticky="w", padx=18, pady=(10, 16))

    # ---------- Acciones ----------
    def aplicar_preset(self, nombre):
        p = PRESETS.get(nombre)
        if not p:
            return
        if p[0] is not None:
            self.v_limite.set(f"{p[0]:g}")
            self.v_extra.set(f"{p[1]:g}")
            self.v_modo.set(p[2])
        self.nota_preset.configure(text=p[3])
        self.calcular()

    def importar_csv(self):
        ruta = filedialog.askopenfilename(title="Importar ventas (CSV o texto)",
                                          filetypes=[("CSV / texto", "*.csv *.txt"), ("Todos", "*.*")])
        if not ruta:
            return
        try:
            with open(ruta, encoding="utf-8-sig", errors="replace") as f:
                lineas = f.read().splitlines()
        except OSError as e:
            messagebox.showerror("No se pudo abrir", str(e))
            return
        buenas, malas = 0, 0
        for linea in lineas:
            v = venta_de_fila(linea)
            if v is not None:
                self.ventas.append(v)
                buenas += 1
            elif linea.strip():
                malas += 1
        self.calcular()
        detalle = f"\n{malas} línea(s) ignoradas (encabezados o texto)." if malas else ""
        messagebox.showinfo("Importación CSV", f"{buenas} venta(s) importadas.{detalle}")

    def agregar(self):
        valor = numero(self.monto.get(), None)
        if valor is None or valor <= 0:
            messagebox.showwarning("Monto no válido", "Escribe un número mayor que cero.")
            return
        self.ventas.append(valor)
        self.monto.delete(0, "end")
        self.calcular()

    def quitar(self, i):
        self.ventas.pop(i)
        self.calcular()

    def limpiar(self):
        self.ventas.clear()
        self.calcular()

    def extra_de(self, venta, limite, extra):
        if venta <= limite:
            return 0.0
        base = venta - limite if self.v_modo.get() == "Lo que se pasa" else venta
        return base * extra / 100

    def calcular(self):
        total = sum(self.ventas)
        limite = numero(self.v_limite.get())
        extra = numero(self.v_extra.get())

        # si el usuario editó los valores de un preset, pasa a Personalizado
        p = PRESETS.get(self.v_preset.get())
        if p and p[0] is not None and (numero(self.v_limite.get()) != p[0]
                                       or numero(self.v_extra.get()) != p[1]
                                       or self.v_modo.get() != p[2]):
            self.v_preset.set("Personalizado")
            self.nota_preset.configure(text=PRESETS["Personalizado"][3])

        for w in self.lista.winfo_children():
            w.destroy()
        if not self.ventas:
            mono(self.lista, "Agrega tu primera venta ⬆ o importa un CSV ⬇", GRIS, 12).pack(pady=30, padx=10)
        for i, v in enumerate(self.ventas):
            pasa = v > limite
            fila = ctk.CTkFrame(self.lista, fg_color="#2A2010" if pasa else "transparent", corner_radius=8)
            fila.grid(row=i, column=0, sticky="ew", pady=2)
            fila.grid_columnconfigure(0, weight=1)
            txt = dinero(v) + (f"   ⚠ extra {dinero(self.extra_de(v, limite, extra))}" if pasa else "")
            ctk.CTkLabel(fila, text=txt, text_color=AVISO if pasa else TEXTO,
                         font=ctk.CTkFont(family=SANS, size=14)).grid(row=0, column=0, sticky="w", padx=10, pady=4)
            ctk.CTkButton(fila, text="✕", width=30, height=26, fg_color="transparent", text_color=GRIS,
                          hover_color="#3A1515", command=lambda i=i: self.quitar(i)
                          ).grid(row=0, column=1, padx=4)

        for w in self.tabla.winfo_children():
            w.destroy()
        for c, t in enumerate(("PORCENTAJE", "IMPUESTO", "TE QUEDA")):
            mono(self.tabla, t, GRIS, 10).grid(row=0, column=c, sticky="w", pady=(0, 4))
        for r, var in enumerate(self.v_pcts, start=1):
            p = numero(var.get())
            imp = total * p / 100
            vals = (f"{p:g}%", dinero(imp), dinero(total - imp))
            cols = (ACENTO, TEXTO, GRIS)
            for c, t in enumerate(vals):
                ctk.CTkLabel(self.tabla, text=t, text_color=cols[c],
                             font=ctk.CTkFont(family=SANS, size=16, weight="bold" if c == 0 else "normal")
                             ).grid(row=r, column=c, sticky="w", pady=4)

        pasadas = [v for v in self.ventas if v > limite]
        ext = sum(self.extra_de(v, limite, extra) for v in pasadas)
        self.s_total.configure(text=dinero(total))
        self.s_pasan.configure(text=str(len(pasadas)))
        self.s_extra.configure(text=dinero(ext))
        if pasadas:
            self.aviso.configure(text_color=AVISO,
                                 text=f"⚠ {len(pasadas)} venta(s) pasan de {dinero(limite)}. "
                                      f"Extra del {extra:g}% a pagar: {dinero(ext)}")
        elif limite > 0:
            self.aviso.configure(text_color=GRIS, text=f"✔ Ninguna venta pasa de {dinero(limite)}.")
        else:
            self.aviso.configure(text_color=GRIS, text=f"✔ Extra del {extra:g}% aplicado a todas las ventas.")


if __name__ == "__main__":
    App().mainloop()
