import tkinter as tk
from tkinter import ttk, scrolledtext
from datetime import datetime, time
from dataclasses import dataclass
from typing import Dict, List, Optional
import random

try:
    import pytz
    LONDON_TZ = pytz.timezone("Europe/London")
except Exception:
    LONDON_TZ = None

MAJOR_PAIRS = ["EURUSD", "GBPUSD", "USDJPY", "AUDUSD", "USDCAD", "EURGBP", "EURJPY"]

# ====================== DATA ======================
@dataclass
class Position:
    pair: str
    direction: str
    entry_price: float
    entry_time: datetime
    stop: float
    size: float
    trail_stop: float
    atr: float
    risk_usd: float = 3.0

@dataclass
class Signal:
    pair: str
    direction: str
    entry: float
    stop: float
    atr: float
    reason: str
    timestamp: datetime


class ApexBot:
    def __init__(self, risk_usd: float = 3.0, max_trades: int = 2):
        self.risk_usd = risk_usd
        self.max_trades = max_trades
        self.equity = 10000.0
        self.positions: Dict[str, Position] = {}
        self.closed: List[dict] = []
        self.logs: List[str] = []
        self.running = False
        self.selected_pairs = ["EURUSD", "GBPUSD", "USDJPY"]

    def log(self, msg: str):
        ts = datetime.now().strftime("%H:%M:%S")
        self.logs.insert(0, f"[{ts}] {msg}")
        self.logs = self.logs[:120]

    def is_london(self) -> bool:
        now = datetime.now(LONDON_TZ).time() if LONDON_TZ else datetime.now().time()
        return time(7, 0) <= now <= time(16, 0)

    def calc_size(self, entry: float, stop: float, pair: str) -> float:
        risk = self.risk_usd
        dist = abs(entry - stop)
        if dist <= 0:
            return 0.0
        pip = 0.01 if "JPY" in pair else 0.0001
        stop_pips = dist / pip
        dollar_per_pip = 9.0 if "JPY" in pair else 10.0
        lots = risk / (stop_pips * dollar_per_pip)
        return max(0.01, round(lots, 2))

    def generate_demo_signal(self, pair: str) -> Optional[Signal]:
        """Demo signals with simple prices around $1."""
        if not self.is_london():
            return None
        if pair in self.positions or len(self.positions) >= self.max_trades:
            return None

        if random.random() > 0.90:
            direction = random.choice(["long", "short"])
            entry = round(random.uniform(0.95, 1.15), 2)   # Entry near $1
            atr = 0.08

            if direction == "long":
                stop = round(entry - atr * 1.5, 2)
            else:
                stop = round(entry + atr * 1.5, 2)

            return Signal(
                pair=pair,
                direction=direction,
                entry=entry,
                stop=stop,
                atr=atr,
                reason="Demo trade (Entry ~$1)",
                timestamp=datetime.now()
            )
        return None

    def open_position(self, sig: Signal):
        size = self.calc_size(sig.entry, sig.stop, sig.pair)
        pos = Position(
            pair=sig.pair,
            direction=sig.direction,
            entry_price=sig.entry,
            entry_time=sig.timestamp,
            stop=sig.stop,
            size=size,
            trail_stop=sig.stop,
            atr=sig.atr,
            risk_usd=self.risk_usd
        )
        self.positions[sig.pair] = pos
        self.log(f"OPEN  {sig.pair} {sig.direction.upper():5} @ {sig.entry:.2f}  |  SL {sig.stop:.2f}  |  {size} lots  |  Risk ${self.risk_usd}")

    def close_position(self, pair: str, exit_price: float, reason: str):
        if pair not in self.positions:
            return
        pos = self.positions[pair]
        risk = abs(pos.entry_price - pos.stop)
        if risk == 0:
            pnl_r = 0.0
        else:
            if pos.direction == "long":
                pnl_r = (exit_price - pos.entry_price) / risk
            else:
                pnl_r = (pos.entry_price - exit_price) / risk

        pnl_usd = pnl_r * self.risk_usd
        self.equity += pnl_usd

        self.closed.append({
            "time": datetime.now().strftime("%H:%M:%S"),
            "pair": pair,
            "direction": pos.direction,
            "entry": pos.entry_price,
            "exit": exit_price,
            "pnl_R": round(pnl_r, 2),
            "pnl_USD": round(pnl_usd, 2)
        })
        self.log(f"CLOSE {pair} {pos.direction.upper():5} @ {exit_price:.2f}  |  {pnl_r:+.2f}R  (${pnl_usd:+.2f})  |  Equity ${self.equity:,.2f}")
        del self.positions[pair]


# ====================== APP ======================
class ApexApp(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title("Apex Trend Bot")
        self.geometry("1180x750")
        self.minsize(1000, 650)
        self.configure(bg="#0f1115")

        # Colors
        self.bg = "#0f1115"
        self.card = "#1a1d24"
        self.accent = "#3b82f6"
        self.green = "#22c55e"
        self.red = "#ef4444"
        self.text = "#e2e8f0"
        self.muted = "#94a3b8"

        self.bot = ApexBot(risk_usd=3.0)
        self.after_id = None

        self.setup_style()
        self.build_ui()
        self.update_ui()

    def setup_style(self):
        style = ttk.Style(self)
        style.theme_use("clam")

        style.configure(".", background=self.bg, foreground=self.text, fieldbackground=self.card)
        style.configure("TFrame", background=self.bg)
        style.configure("Card.TFrame", background=self.card)
        style.configure("TLabel", background=self.bg, foreground=self.text, font=("Segoe UI", 10))
        style.configure("Title.TLabel", font=("Segoe UI", 16, "bold"), foreground="white")
        style.configure("Muted.TLabel", foreground=self.muted, font=("Segoe UI", 9))
        style.configure("Metric.TLabel", font=("Segoe UI", 18, "bold"), foreground="white")
        style.configure("TButton", font=("Segoe UI", 10, "bold"), padding=8)
        style.configure("Treeview", background=self.card, foreground=self.text,
                        fieldbackground=self.card, rowheight=28, font=("Segoe UI", 9))
        style.configure("Treeview.Heading", background="#252830", foreground=self.muted,
                        font=("Segoe UI", 9, "bold"))
        style.map("Treeview", background=[("selected", "#1e3a5f")])

    def build_ui(self):
        # ===== HEADER =====
        header = tk.Frame(self, bg="#161920", height=60)
        header.pack(fill="x")
        header.pack_propagate(False)

        tk.Label(header, text="⚡  APEX TREND BOT", bg="#161920", fg="white",
                 font=("Segoe UI", 14, "bold")).pack(side="left", padx=20, pady=15)

        self.session_lbl = tk.Label(header, text="", bg="#161920", font=("Segoe UI", 10, "bold"))
        self.session_lbl.pack(side="right", padx=20)

        self.status_lbl = tk.Label(header, text="● STOPPED", bg="#161920", fg=self.red,
                                   font=("Segoe UI", 10, "bold"))
        self.status_lbl.pack(side="right", padx=10)

        # ===== CONTROL BAR =====
        ctrl = tk.Frame(self, bg=self.bg, pady=12)
        ctrl.pack(fill="x", padx=20)

        tk.Label(ctrl, text="Risk $", bg=self.bg, fg=self.muted).pack(side="left")
        self.risk_var = tk.DoubleVar(value=3.0)
        tk.Spinbox(ctrl, from_=1, to=50, increment=0.5, textvariable=self.risk_var,
                   width=6, bg=self.card, fg="white", buttonbackground=self.card,
                   relief="flat").pack(side="left", padx=(6, 18))

        tk.Label(ctrl, text="Max Trades", bg=self.bg, fg=self.muted).pack(side="left")
        self.max_var = tk.IntVar(value=2)
        tk.Spinbox(ctrl, from_=1, to=5, textvariable=self.max_var, width=4,
                   bg=self.card, fg="white", buttonbackground=self.card,
                   relief="flat").pack(side="left", padx=(6, 18))

        self.btn_start = tk.Button(ctrl, text="▶  START", command=self.start_bot,
                                   bg=self.green, fg="white", font=("Segoe UI", 10, "bold"),
                                   relief="flat", padx=16, pady=6, cursor="hand2")
        self.btn_start.pack(side="left", padx=4)

        self.btn_stop = tk.Button(ctrl, text="⏹  STOP", command=self.stop_bot,
                                  bg="#334155", fg="white", font=("Segoe UI", 10, "bold"),
                                  relief="flat", padx=16, pady=6, state="disabled", cursor="hand2")
        self.btn_stop.pack(side="left", padx=4)

        # ===== METRICS CARDS =====
        metrics = tk.Frame(self, bg=self.bg)
        metrics.pack(fill="x", padx=20, pady=(5, 15))

        self.metric_vars = {}
        for title, key in [("EQUITY", "equity"), ("OPEN", "open"), ("CLOSED", "closed"), ("RISK / TRADE", "risk")]:
            card = tk.Frame(metrics, bg=self.card, padx=20, pady=12)
            card.pack(side="left", padx=(0, 12), fill="x", expand=True)
            tk.Label(card, text=title, bg=self.card, fg=self.muted, font=("Segoe UI", 8)).pack(anchor="w")
            var = tk.StringVar()
            self.metric_vars[key] = var
            tk.Label(card, textvariable=var, bg=self.card, fg="white",
                     font=("Segoe UI", 16, "bold")).pack(anchor="w")

        # ===== MAIN CONTENT =====
        content = tk.Frame(self, bg=self.bg)
        content.pack(fill="both", expand=True, padx=20, pady=(0, 15))

        # Left column
        left = tk.Frame(content, bg=self.bg)
        left.pack(side="left", fill="both", expand=True, padx=(0, 10))

        # Open positions card
        pos_card = tk.Frame(left, bg=self.card)
        pos_card.pack(fill="x", pady=(0, 12))
        tk.Label(pos_card, text="OPEN POSITIONS", bg=self.card, fg=self.muted,
                 font=("Segoe UI", 9, "bold")).pack(anchor="w", padx=12, pady=(10, 4))

        cols = ("Pair", "Dir", "Entry", "Stop", "Size", "Risk")
        self.pos_tree = ttk.Treeview(pos_card, columns=cols, show="headings", height=6)
        for c in cols:
            self.pos_tree.heading(c, text=c)
            self.pos_tree.column(c, width=90, anchor="center")
        self.pos_tree.pack(fill="x", padx=8, pady=(0, 10))

        # Closed trades card
        closed_card = tk.Frame(left, bg=self.card)
        closed_card.pack(fill="both", expand=True)
        tk.Label(closed_card, text="RECENT CLOSED TRADES", bg=self.card, fg=self.muted,
                 font=("Segoe UI", 9, "bold")).pack(anchor="w", padx=12, pady=(10, 4))

        cols2 = ("Time", "Pair", "Dir", "Entry", "Exit", "R", "USD")
        self.closed_tree = ttk.Treeview(closed_card, columns=cols2, show="headings", height=9)
        for c in cols2:
            self.closed_tree.heading(c, text=c)
            self.closed_tree.column(c, width=80, anchor="center")
        self.closed_tree.pack(fill="both", expand=True, padx=8, pady=(0, 10))

        # Right column - Log
        right = tk.Frame(content, bg=self.card, width=340)
        right.pack(side="right", fill="y")
        right.pack_propagate(False)

        tk.Label(right, text="ACTIVITY LOG", bg=self.card, fg=self.muted,
                 font=("Segoe UI", 9, "bold")).pack(anchor="w", padx=12, pady=(10, 4))

        self.log_text = scrolledtext.ScrolledText(right, bg="#12141a", fg="#cbd5e1",
                                                  font=("Consolas", 9), relief="flat",
                                                  insertbackground="white")
        self.log_text.pack(fill="both", expand=True, padx=8, pady=(0, 10))
        self.log_text.configure(state="disabled")

        # Pair selection
        pair_bar = tk.Frame(self, bg="#161920", pady=8)
        pair_bar.pack(fill="x")
        tk.Label(pair_bar, text="  Pairs:", bg="#161920", fg=self.muted).pack(side="left", padx=(12, 6))
        self.pair_vars = {}
        for p in MAJOR_PAIRS:
            var = tk.BooleanVar(value=p in self.bot.selected_pairs)
            self.pair_vars[p] = var
            cb = tk.Checkbutton(pair_bar, text=p, variable=var, bg="#161920", fg=self.text,
                                selectcolor="#1e293b", activebackground="#161920",
                                activeforeground="white", font=("Segoe UI", 9))
            cb.pack(side="left", padx=6)

    # ===== LOGIC =====
    def start_bot(self):
        self.bot.risk_usd = self.risk_var.get()
        self.bot.max_trades = self.max_var.get()
        self.bot.selected_pairs = [p for p, v in self.pair_vars.items() if v.get()]
        self.bot.running = True
        self.btn_start.config(state="disabled", bg="#166534")
        self.btn_stop.config(state="normal", bg=self.red)
        self.status_lbl.config(text="● RUNNING", fg=self.green)
        self.bot.log("Bot STARTED — Demo mode with 100x exits")
        self.schedule_tick()

    def stop_bot(self):
        self.bot.running = False
        if self.after_id:
            self.after_cancel(self.after_id)
            self.after_id = None
        self.btn_start.config(state="normal", bg=self.green)
        self.btn_stop.config(state="disabled", bg="#334155")
        self.status_lbl.config(text="● STOPPED", fg=self.red)
        self.bot.log("Bot STOPPED")
        self.update_ui()

    def schedule_tick(self):
        if self.bot.running:
            self.bot_tick()
            self.after_id = self.after(2500, self.schedule_tick)

    def bot_tick(self):
        # Generate new signals
        for pair in self.bot.selected_pairs:
            sig = self.bot.generate_demo_signal(pair)
            if sig:
                self.bot.open_position(sig)

        # Close with 100× exit price
        if self.bot.positions and random.random() > 0.78:
            pair = list(self.bot.positions.keys())[0]
            pos = self.bot.positions[pair]

            if pos.direction == "long":
                exit_p = round(pos.entry_price * 100, 2)      # 100× entry
            else:
                exit_p = round(pos.entry_price / 100, 4)      # inverse for short

            self.bot.close_position(pair, exit_p, "demo 100x exit")

        self.update_ui()

    def update_ui(self):
        self.metric_vars["equity"].set(f"${self.bot.equity:,.2f}")
        self.metric_vars["open"].set(str(len(self.bot.positions)))
        self.metric_vars["closed"].set(str(len(self.bot.closed)))
        self.metric_vars["risk"].set(f"${self.bot.risk_usd:.1f}")

        if self.bot.is_london():
            self.session_lbl.config(text="🟢  LONDON SESSION ACTIVE", fg=self.green)
        else:
            self.session_lbl.config(text="🔴  OUTSIDE LONDON SESSION", fg=self.red)

        # Positions
        for i in self.pos_tree.get_children():
            self.pos_tree.delete(i)
        for p in self.bot.positions.values():
            self.pos_tree.insert("", "end", values=(
                p.pair, p.direction.upper(), f"{p.entry_price:.2f}",
                f"{p.trail_stop:.2f}", p.size, f"${p.risk_usd:.1f}"))

        # Closed trades
        for i in self.closed_tree.get_children():
            self.closed_tree.delete(i)
        for t in reversed(self.bot.closed[-15:]):
            self.closed_tree.insert("", "end", values=(
                t["time"], t["pair"], t["direction"].upper(),
                f"{t['entry']:.2f}", f"{t['exit']:.2f}",
                f"{t['pnl_R']:+.2f}", f"${t['pnl_USD']:+.2f}"))

        # Log
        self.log_text.configure(state="normal")
        self.log_text.delete("1.0", "end")
        self.log_text.insert("end", "\n".join(self.bot.logs[:50]))
        self.log_text.configure(state="disabled")


if __name__ == "__main__":
    app = ApexApp()
    app.mainloop()
