"""
UI tab definitions for TapIN MMA.

Each class is responsible for rendering a single tab:
- FighterComparisonTab
- UpcomingEventsTab
- BettingInfoTab
- FightPredictorTab
- SettingsTab
"""

import re
from typing import List, Dict, Any, Optional

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

from config import DISPLAY_WEIGHT_CLASSES, base_class_from_label, THEMES
from services import FighterService, EventsService, EmailService, OddsService


class FighterComparisonTab:
    def __init__(self, fighter_service: FighterService):
        self.fighter_service = fighter_service

    def render(self, container):
        fighters = self.fighter_service.get_fighters()
        with container:
            st.header("MMA Fighter Comparison")
            selected_label = st.selectbox("Weight Class", DISPLAY_WEIGHT_CLASSES, index=0)

            base = base_class_from_label(selected_label)
            if base:
                filtered = [
                    f
                    for f in fighters
                    if base.lower() in str(f.get("category", "")).lower()
                ]
            else:
                filtered = fighters

            names = [f.get("name") for f in filtered]
            if len(names) < 2:
                st.info("Not enough fighters loaded to compare yet.")
                return

            f1_name = st.selectbox("Fighter 1", names, index=0)
            f2_name = st.selectbox("Fighter 2", names, index=min(1, len(names) - 1))

            f1 = next((f for f in filtered if f.get("name") == f1_name), None)
            f2 = next((f for f in filtered if f.get("name") == f2_name), None)

            col1, col2 = st.columns(2)
            if f1 and f2:
                with col1:
                    with st.expander(f"{f1.get('name', '')} Image", expanded=True):
                        st.image(f1.get("imgUrl", ""), width=250)
                with col2:
                    with st.expander(f"{f2.get('name', '')} Image", expanded=True):
                        st.image(f2.get("imgUrl", ""), width=250)

                with st.expander("Fighter Stats", expanded=False):
                    c1, c2 = st.columns(2)

                    def render_stats(col, f):
                        with col:
                            st.subheader(f.get("name", "N/A"))
                            st.markdown(
                                f"**Record:** {f.get('wins', '0')}-{f.get('losses', '0')}-{f.get('draws', '0')}"
                            )
                            st.markdown(
                                f"**Weight Class:** {f.get('category', 'N/A')}"
                            )
                            st.markdown(f"**Height:** {f.get('height', 'N/A')}")
                            st.markdown(f"**Reach:** {f.get('reach', 'N/A')}")
                            st.markdown(
                                f"**Fighting Style:** {f.get('fightingStyle', 'N/A')}"
                            )
                            st.markdown(
                                f"**Trains At:** {f.get('trainsAt', 'N/A')}"
                            )

                    render_stats(c1, f1)
                    render_stats(c2, f2)

                with st.expander("Fight Record Comparison Chart", expanded=False):
                    chart_data = pd.DataFrame(
                        {
                            "Fighter": [f1.get("name")] * 3 + [f2.get("name")] * 3,
                            "Result": ["Wins", "Losses", "Draws"] * 2,
                            "Count": [
                                int(f1.get("wins", 0)),
                                int(f1.get("losses", 0)),
                                int(f1.get("draws", 0)),
                                int(f2.get("wins", 0)),
                                int(f2.get("losses", 0)),
                                int(f2.get("draws", 0)),
                            ],
                        }
                    )
                    fig = px.bar(
                        chart_data,
                        x="Result",
                        y="Count",
                        color="Fighter",
                        barmode="group",
                    )
                    st.plotly_chart(fig, use_container_width=True)


class UpcomingEventsTab:
    def __init__(self, events_service: EventsService, email_service: EmailService):
        self.events_service = events_service
        self.email_service = email_service

    def render(self, container):
        with container:
            st.header("Upcoming MMA Events by Region")

            region_name = st.selectbox("Choose a Region", list(self.events_service.region_map.keys()))
            rid = self.events_service.region_map[region_name]
            events = self.events_service.fetch_upcoming_events(rid)

            if events:
                for ev in events:
                    with st.expander(f"{ev.get('organization')} – {ev.get('datetime')}"):
                        st.markdown(f"**Main Event:** {ev.get('main_event', 'TBD')}")
                        st.markdown(
                            f"**Weight Class:** {ev.get('weight_class', 'N/A')}"
                        )
                        st.markdown(f"**City:** {ev.get('city', 'Unknown')}")
                        st.markdown(f"**Broadcast:** {ev.get('broadcast', 'N/A')}")
            else:
                st.info("No events returned for this region right now.")

            if events:
                rows = []
                for ev in events[:50]:
                    city = ev.get("city")
                    if not city:
                        continue
                    coords = self.events_service.geocode_city(city)
                    if coords:
                        lat, lon = coords
                        rows.append(
                            {
                                "city": city,
                                "organization": ev.get("organization", ""),
                                "date": ev.get("datetime", ""),
                                "lat": lat,
                                "lon": lon,
                            }
                        )
                if rows:
                    df_geo = pd.DataFrame(rows)
                    fig_geo = px.scatter_geo(
                        df_geo,
                        lat="lat",
                        lon="lon",
                        hover_name="city",
                        hover_data=["organization", "date"],
                    )
                    fig_geo.update_geos(
                        fitbounds="locations", showcountries=True
                    )
                    st.plotly_chart(fig_geo, use_container_width=True)
                else:
                    st.caption(
                        "Could not geocode event cities to plot a map (or no city data provided)."
                    )

            st.subheader("Get Notified of Upcoming Fights")
            with st.form("email_form"):
                email = st.text_input("Enter your email to get notifications")
                consent = st.checkbox(
                    "I consent to receive emails about upcoming MMA events.", value=True
                )
                submitted = st.form_submit_button("Subscribe")
                if submitted:
                    if email and consent:
                        self.email_service.subscribe_email(email, consent)
                        st.success(
                            f"✅ {email} has been added to the notification list!"
                        )
                    else:
                        st.error("Email and consent are required.")

            with st.expander("Unsubscribe"):
                u_email = st.text_input("Enter your email to unsubscribe")
                if st.button("Unsubscribe"):
                    if u_email:
                        st.success(
                            "You have been unsubscribed."
                            if self.email_service.unsubscribe_email(u_email)
                            else "Email not found."
                        )

            if st.session_state.get("is_admin", False):
                st.divider()
                st.subheader("Admin: Send a digest now (Mailjet)")
                colA, colB = st.columns(2)
                with colA:
                    if st.button("Send digest to all subscribers (Mailjet)"):
                        result = self.email_service.send_digest_to_all(events)
                        st.json(result)
                with colB:
                    st.text_area(
                        "Preview Email",
                        self.email_service.render_email_preview(events),
                        height=180,
                    )


class BettingInfoTab:
    def __init__(self, odds_service: OddsService):
        self.odds_service = odds_service

    def render(self, container):
        from datetime import datetime, timedelta, timezone

        with container:
            st.header("Betting Odds")
            st.caption(
                "Prices are American odds; implied probabilities; MMA odds pulled from The Odds API."
            )

            df_odds, last_iso = self.odds_service.fetch_odds()

            if last_iso:
                st.markdown(f"**Last Fetched:** {last_iso}")
                try:
                    iso_dt = datetime.fromisoformat(last_iso.replace("Z", "+00:00"))
                    if (datetime.now(timezone.utc) - iso_dt) > timedelta(minutes=30):
                        st.warning(
                            "⚠️ Odds may be stale (fetched more than 30 minutes ago)."
                        )
                except Exception:
                    st.warning("⚠️ Could not parse last fetched time.")
            else:
                st.markdown("**Last Fetched:** —")

            if df_odds.empty:
                if last_iso is None:
                    st.error(
                        "No odds available. Check that `odds_api_key` is set under `[api]` in your `.streamlit/secrets.toml`."
                    )
                else:
                    st.info(
                        "The Odds API returned no MMA odds at this time. "
                        "This can happen if there are no live or upcoming fights for the selected regions/markets."
                    )
                return

            matchups = sorted(df_odds["matchup"].unique()) if not df_odds.empty else []
            pick = st.selectbox("Select a matchup", ["All"] + matchups)

            filt = df_odds if pick == "All" else df_odds[df_odds["matchup"] == pick]

            if filt.empty:
                st.info("No odds available right now for that matchup.")
                return

            df_show = filt.copy()
            df_show["Implied Prob"] = df_show["implied"].apply(
                lambda x: f"{x*100:.1f}%" if pd.notnull(x) else "—"
            )
            df_show = df_show.rename(
                columns={
                    "matchup": "Matchup",
                    "book": "Book",
                    "team": "Fighter",
                    "price": "Odds",
                    "last_update_iso": "Book Last Update",
                }
            )[
                ["Matchup", "Book", "Fighter", "Odds", "Implied Prob", "Book Last Update"]
            ]
            st.dataframe(df_show, use_container_width=True)


class FightPredictorTab:
    def __init__(self, fighter_service: FighterService):
        self.fighter_service = fighter_service

    def _to_cm(self, val: str) -> Optional[float]:
        if not val:
            return None
        s = str(val).lower()
        try:
            if "cm" in s:
                return float(re.findall(r"[\d.]+", s)[0])
            m = re.match(r"(\d+)'(\d+)", s)
            if m:
                return int(m.group(1)) * 30.48 + int(m.group(2)) * 2.54
            if '"' in s or "in" in s:
                return float(re.findall(r"[\d.]+", s)[0]) * 2.54
        except Exception:
            return None
        return None

    def _age_int(self, f: Dict[str, Any]) -> Optional[int]:
        try:
            return int(f.get("age"))
        except Exception:
            return None

    def _score(self, f: Dict[str, Any]) -> float:
        w = int(f.get("wins", 0))
        l = int(f.get("losses", 0))
        r = self._to_cm(f.get("reach", "") or "")
        h = self._to_cm(f.get("height", "") or "")
        a = self._age_int(f)
        s_val = w * 1.0 - l * 0.8
        if r:
            s_val += (r - 175) * 0.1
        if h:
            s_val += (h - 178) * 0.05
        if a:
            s_val += max(0, 34 - a) * 0.2
        return s_val

    def render(self, container):
        fighters = self.fighter_service.get_fighters()
        with container:
            st.header("Fight Predictor")
            st.caption(
                "Baseline rule-based model with a TapIN Pick. Toggle predictions below."
            )
            enable_pred = st.toggle("Enable Predictions", value=True)
            st.markdown(
                "<small><em>Disclaimer: Experimental model — not betting advice.</em></small>",
                unsafe_allow_html=True,
            )

            selected_label2 = st.selectbox(
                "Select Weight Class",
                DISPLAY_WEIGHT_CLASSES,
                index=0,
                key="pred_class",
            )
            base2 = base_class_from_label(selected_label2)
            class_filtered = (
                [
                    f
                    for f in fighters
                    if (base2.lower() in str(f.get("category", "")).lower())
                ]
                if base2
                else fighters
            )

            names2 = [f.get("name") for f in class_filtered]
            if len(names2) < 2:
                st.warning("Not enough fighters in this class to compare.")
                return

            f1_name = st.selectbox("Fighter 1", names2, index=0, key="pred1")
            f2_name = st.selectbox("Fighter 2", names2, index=1, key="pred2")

            if st.button("Predict the Winner"):
                if not enable_pred:
                    st.info("Predictions are disabled.")
                    return

                f1 = next(
                    (f for f in class_filtered if f.get("name") == f1_name), None
                )
                f2 = next(
                    (f for f in class_filtered if f.get("name") == f2_name), None
                )
                if f1 and f2:
                    s1, s2 = self._score(f1), self._score(f2)
                    mn = min(0, s1, s2)
                    s1n, s2n = max(0.01, s1 - mn + 0.01), max(
                        0.01, s2 - mn + 0.01
                    )
                    p1, p2 = s1n / (s1n + s2n), s2n / (s1n + s2n)
                    st.markdown(f"**{f1_name} Confidence:** {p1*100:.2f}%")
                    st.markdown(f"**{f2_name} Confidence:** {p2*100:.2f}%")
                    st.success(
                        f"**TapIN Pick:** {f1_name if p1 > p2 else f2_name}"
                    )
                else:
                    st.error("Could not retrieve both fighters.")


class SettingsTab:
    def __init__(self, safe_rerun):
        self.safe_rerun = safe_rerun

    def render(self, container):
        with container:
            st.header("Settings")

            bg_color = st.color_picker("Choose App Background Color", "#0e1117")
            st.markdown(
                f"<style>.stApp {{ background-color: {bg_color}; }}</style>",
                unsafe_allow_html=True,
            )

            st.subheader("Theme")
            theme_choice = st.selectbox(
                "Select Theme",
                list(THEMES.keys()),
                index=list(THEMES.keys()).index(
                    st.session_state.get("theme", "Dark")
                ),
            )

            if theme_choice != st.session_state.get("theme"):
                st.session_state["theme"] = theme_choice
                self.safe_rerun()

            st.subheader("Admin")
            admin_input = st.text_input(
                "Admin password", type="password", key="admin_password_input"
            )
            col1, col2 = st.columns(2)

            with col1:
                if st.button("Login as Admin"):
                    try:
                        admin_pw = st.secrets["api"]["admin_password"]
                    except Exception:
                        admin_pw = None
                    if not admin_pw:
                        st.error(
                            "Admin password not configured in secrets.toml ([api].admin_password)."
                        )
                    elif admin_input == admin_pw:
                        st.session_state["is_admin"] = True
                        st.success("Logged in as admin.")
                        self.safe_rerun()
                    else:
                        st.error("Incorrect admin password.")

            with col2:
                if st.button("Logout Admin"):
                    st.session_state["is_admin"] = False
                    st.success("Logged out of admin.")
                    self.safe_rerun()
