"""Streamlit interface for the Smart Card Benefit Recommender."""

from __future__ import annotations

from datetime import date
from pathlib import Path

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

from analytics import (
    build_dashboard_metrics,
    missed_rewards_this_month,
    monthly_spending,
    most_used_card,
    most_valuable_card,
    rewards_by_card,
    spending_by_category,
    transactions_to_dataframe,
)
from cards import (
    CARD_TYPES,
    CATEGORIES,
    CATEGORY_RATE_COLUMNS,
    REWARD_TYPES,
    validate_card_data,
)
from database import (
    add_card,
    add_transaction,
    delete_card,
    get_cards,
    get_transactions,
    initialize_database,
    load_demo_cards,
    reset_database,
    update_card,
)
from recommender import calculate_reward, get_card_reward_rate, recommend_best_card
from utils import clean_text, format_currency, format_percentage, get_custom_css


NAVIGATION_ITEMS = [
    "🏠 Dashboard",
    "✨ Find Best Card",
    "💳 My Cards",
    "➕ Add Card",
    "📊 Analytics",
    "📜 Transaction History",
    "⚙️ Settings / About",
]

BASE_DIR = Path(__file__).resolve().parent
LOGO_PATH = BASE_DIR / "assets" / "logo.png"


def rerun_app() -> None:
    """Rerun the app after database-changing actions."""
    if hasattr(st, "rerun"):
        st.rerun()
    else:
        st.experimental_rerun()


def safe_index(options: list[str], value: str) -> int:
    """Return the index of a value, falling back to the first option."""
    try:
        return options.index(value)
    except ValueError:
        return 0


def render_header(title: str, subtitle: str) -> None:
    """Render a consistent page header."""
    st.markdown(
        f"""
        <div class="app-header">
            <h1>{title}</h1>
            <p>{subtitle}</p>
        </div>
        """,
        unsafe_allow_html=True,
    )


def render_empty_state(message: str) -> None:
    """Render a polished empty-state box."""
    st.markdown(
        f"""<div class="empty-state">{message}</div>""",
        unsafe_allow_html=True,
    )


def collect_reward_rates(prefix: str, existing_card: dict | None = None) -> dict[str, float]:
    """Create category reward-rate inputs and return their values."""
    reward_rates: dict[str, float] = {}
    columns = st.columns(3)

    for index, (category, column_name) in enumerate(CATEGORY_RATE_COLUMNS.items()):
        with columns[index % 3]:
            default_value = 0.0
            if existing_card:
                default_value = float(existing_card.get(column_name) or 0)

            reward_rates[column_name] = st.number_input(
                f"{category} (%)",
                min_value=0.0,
                max_value=100.0,
                value=default_value,
                step=0.1,
                key=f"{prefix}_{column_name}",
            )

    return reward_rates


def build_card_form_payload(
    card_name: str,
    bank_name: str,
    card_type: str,
    annual_fee: float,
    reward_type: str,
    reward_rates: dict[str, float],
) -> dict:
    """Collect card form values into one dictionary."""
    payload = {
        "card_name": card_name,
        "bank_name": bank_name,
        "card_type": card_type,
        "annual_fee": annual_fee,
        "reward_type": reward_type,
    }
    payload.update(reward_rates)
    return payload


def get_card_summary_html(card: dict) -> str:
    """Build a small visual summary for one saved card."""
    reward_pills = []
    for category, column_name in CATEGORY_RATE_COLUMNS.items():
        reward_pills.append(
            f'<span class="pill">{category}: {format_percentage(card[column_name])}</span>'
        )

    return f"""
    <div class="soft-panel">
        <div class="card-title">{card["card_name"]}</div>
        <div class="card-subtitle">{card["bank_name"]} • {card["card_type"]} • {card["reward_type"]}</div>
        <div>{"".join(reward_pills)}</div>
        <p style="margin-top: 0.8rem; margin-bottom: 0; color: #475569;">
            Annual Fee: <strong>{format_currency(card["annual_fee"])}</strong>
        </p>
    </div>
    """


def prepare_transactions_table(dataframe: pd.DataFrame) -> pd.DataFrame:
    """Format transaction rows for display."""
    if dataframe.empty:
        return pd.DataFrame()

    table = dataframe.copy()
    table["Date"] = table["transaction_date"].dt.strftime("%d %b %Y")
    table["Merchant"] = table["merchant"]
    table["Category"] = table["category"]
    table["Amount"] = table["amount"].apply(format_currency)
    table["Recommended Card"] = table["recommended_card"]
    table["Used Card"] = table["used_card"]
    table["Reward"] = table["actual_reward"].apply(format_currency)
    table["Missed Reward"] = table["missed_reward"].apply(format_currency)

    return table[
        [
            "Date",
            "Merchant",
            "Category",
            "Amount",
            "Recommended Card",
            "Used Card",
            "Reward",
            "Missed Reward",
        ]
    ]


def comparison_to_dataframe(comparison: list[dict]) -> pd.DataFrame:
    """Format recommender comparison rows for Streamlit tables."""
    rows = []
    for rank, row in enumerate(comparison, start=1):
        rows.append(
            {
                "Rank": rank,
                "Card": row["card_name"],
                "Bank": row["bank_name"],
                "Reward Type": row["reward_type"],
                "Reward Rate": format_percentage(row["reward_rate"]),
                "Expected Benefit": format_currency(row["expected_benefit"]),
            }
        )
    return pd.DataFrame(rows)


def render_metric_row(metrics: dict) -> None:
    """Show the main dashboard metric cards."""
    column_one, column_two, column_three = st.columns(3)
    column_four, column_five, column_six = st.columns(3)

    column_one.metric("Total Spending", format_currency(metrics["total_spending"]))
    column_two.metric("Rewards Earned", format_currency(metrics["total_rewards"]))
    column_three.metric("Total Transactions", metrics["total_transactions"])
    column_four.metric("Cards Added", metrics["cards_added"])
    column_five.metric("Potential Savings Missed", format_currency(metrics["missed_rewards"]))
    column_six.metric("Favourite Category", metrics["favourite_category"])


def render_dashboard() -> None:
    """Dashboard page with metrics, recent transactions, and charts."""
    render_header(
        "Smart Card Benefit Recommender",
        "A simple financial dashboard that helps choose the most rewarding card for each transaction.",
    )

    cards = get_cards()
    transactions = get_transactions()
    dataframe = transactions_to_dataframe(transactions)
    metrics = build_dashboard_metrics(transactions, len(cards))
    render_metric_row(metrics)

    st.subheader("Recent Transactions")
    if dataframe.empty:
        render_empty_state("No transactions yet. Use Find Best Card to save your first transaction.")
    else:
        recent = dataframe.sort_values("transaction_date", ascending=False).head(8)
        st.dataframe(prepare_transactions_table(recent), use_container_width=True, hide_index=True)

    chart_one, chart_two = st.columns(2)

    with chart_one:
        st.subheader("Spending by Category")
        category_data = spending_by_category(dataframe)
        if category_data.empty:
            render_empty_state("Category chart will appear after transactions are saved.")
        else:
            figure = px.pie(
                category_data,
                values="amount",
                names="category",
                hole=0.42,
                color_discrete_sequence=px.colors.qualitative.Set2,
            )
            figure.update_layout(margin=dict(l=10, r=10, t=10, b=10), legend_title_text="")
            st.plotly_chart(figure, use_container_width=True)

    with chart_two:
        st.subheader("Rewards by Card")
        rewards_data = rewards_by_card(dataframe)
        if rewards_data.empty:
            render_empty_state("Rewards chart will appear after transaction history exists.")
        else:
            figure = px.bar(
                rewards_data,
                x="used_card",
                y="actual_reward",
                color="used_card",
                color_discrete_sequence=px.colors.qualitative.Pastel,
            )
            figure.update_layout(
                xaxis_title="Card",
                yaxis_title="Reward",
                showlegend=False,
                margin=dict(l=10, r=10, t=10, b=10),
            )
            st.plotly_chart(figure, use_container_width=True)


def render_add_card() -> None:
    """Add-card page."""
    render_header(
        "Add Credit Card",
        "Save a card and define category-wise reward percentages.",
    )

    with st.form("add_card_form", clear_on_submit=True):
        first_column, second_column = st.columns(2)
        with first_column:
            card_name = st.text_input("Card Name", placeholder="Example: HDFC Millennia")
            bank_name = st.text_input("Bank Name", placeholder="Example: HDFC Bank")
            card_type = st.selectbox("Card Type", CARD_TYPES)
        with second_column:
            annual_fee = st.number_input("Annual Fee", min_value=0.0, value=0.0, step=100.0)
            reward_type = st.selectbox("Reward Type", REWARD_TYPES)

        st.markdown("#### Reward Rates by Category")
        reward_rates = collect_reward_rates("add")
        submitted = st.form_submit_button("Save Card", type="primary")

    if submitted:
        payload = build_card_form_payload(
            card_name, bank_name, card_type, annual_fee, reward_type, reward_rates
        )
        errors = validate_card_data(payload)

        if errors:
            for error in errors:
                st.error(error)
        else:
            add_card(payload)
            st.success(f"{clean_text(card_name)} was added successfully.")


def render_my_cards() -> None:
    """My Cards page with view, edit, and delete actions."""
    render_header(
        "My Cards",
        "View, edit, delete, and compare the reward structure of your saved cards.",
    )

    cards = get_cards()
    if not cards:
        render_empty_state("You have not added any credit cards yet. Add at least one card before using the recommender.")
        return

    for card in cards:
        st.markdown(get_card_summary_html(card), unsafe_allow_html=True)

        with st.expander(f"Edit, delete, or view benefits for {card['card_name']}"):
            tab_view, tab_edit, tab_delete = st.tabs(["View Benefits", "Edit", "Delete"])

            with tab_view:
                rates_table = pd.DataFrame(
                    [
                        {
                            "Category": category,
                            "Reward Rate": format_percentage(card[column_name]),
                        }
                        for category, column_name in CATEGORY_RATE_COLUMNS.items()
                    ]
                )
                st.dataframe(rates_table, use_container_width=True, hide_index=True)

            with tab_edit:
                with st.form(f"edit_form_{card['id']}"):
                    first_column, second_column = st.columns(2)
                    with first_column:
                        card_name = st.text_input(
                            "Card Name",
                            value=card["card_name"],
                            key=f"edit_name_{card['id']}",
                        )
                        bank_name = st.text_input(
                            "Bank Name",
                            value=card["bank_name"],
                            key=f"edit_bank_{card['id']}",
                        )
                        card_type = st.selectbox(
                            "Card Type",
                            CARD_TYPES,
                            index=safe_index(CARD_TYPES, card["card_type"]),
                            key=f"edit_type_{card['id']}",
                        )
                    with second_column:
                        annual_fee = st.number_input(
                            "Annual Fee",
                            min_value=0.0,
                            value=float(card["annual_fee"] or 0),
                            step=100.0,
                            key=f"edit_fee_{card['id']}",
                        )
                        reward_type = st.selectbox(
                            "Reward Type",
                            REWARD_TYPES,
                            index=safe_index(REWARD_TYPES, card["reward_type"]),
                            key=f"edit_reward_{card['id']}",
                        )

                    st.markdown("#### Reward Rates by Category")
                    reward_rates = collect_reward_rates(f"edit_{card['id']}", card)
                    submitted = st.form_submit_button("Update Card", type="primary")

                if submitted:
                    payload = build_card_form_payload(
                        card_name,
                        bank_name,
                        card_type,
                        annual_fee,
                        reward_type,
                        reward_rates,
                    )
                    errors = validate_card_data(payload)

                    if errors:
                        for error in errors:
                            st.error(error)
                    else:
                        update_card(card["id"], payload)
                        st.success(f"{clean_text(card_name)} was updated.")
                        rerun_app()

            with tab_delete:
                st.warning("Deleting a card removes it from future recommendations. Existing transaction history remains.")
                confirm_delete = st.checkbox(
                    f"I confirm that I want to delete {card['card_name']}.",
                    key=f"confirm_delete_{card['id']}",
                )
                if st.button("Delete Card", key=f"delete_{card['id']}"):
                    if confirm_delete:
                        delete_card(card["id"])
                        st.success(f"{card['card_name']} was deleted.")
                        rerun_app()
                    else:
                        st.error("Please tick the confirmation box before deleting.")


def render_recommendation_result(result: dict, amount: float, category: str, merchant: str) -> None:
    """Display the result from the recommendation engine."""
    best_cards = result["best_cards"]
    if not best_cards:
        return

    best_card_names = [row["card_name"] for row in best_cards]
    best_card_label = " / ".join(best_card_names)
    first_best_card = best_cards[0]

    title = "BEST CARDS FOR THIS TRANSACTION" if result["is_tie"] else "BEST CARD FOR THIS TRANSACTION"

    st.markdown(
        f"""
        <div class="recommendation-box">
            <h2>🏆 {title}</h2>
            <h3>{best_card_label}</h3>
            <p><strong>Merchant:</strong> {merchant or "Not specified"}</p>
            <p><strong>Transaction:</strong> {format_currency(amount)} &nbsp; | &nbsp;
               <strong>Category:</strong> {category}</p>
            <p><strong>Reward Rate:</strong> {format_percentage(first_best_card["reward_rate"])} &nbsp; | &nbsp;
               <strong>Expected Benefit:</strong> {format_currency(result["highest_benefit"])}</p>
            <p>Why this card? It provides the highest estimated benefit among your saved cards for this category.</p>
        </div>
        """,
        unsafe_allow_html=True,
    )

    if result["is_tie"]:
        st.info(
            "Multiple cards give the same highest benefit. Any of the highlighted cards is equally beneficial for this transaction."
        )

    st.subheader("Reward Comparison")
    st.dataframe(comparison_to_dataframe(result["comparison"]), use_container_width=True, hide_index=True)


def render_find_best_card() -> None:
    """Main recommendation page."""
    render_header(
        "Find Best Card",
        "Enter a transaction and compare expected rewards across all saved cards.",
    )

    cards = get_cards()
    if not cards:
        render_empty_state("You have not added any credit cards yet. Add at least one card before using the recommender.")
        return

    with st.form("recommendation_form"):
        first_column, second_column = st.columns(2)
        with first_column:
            amount = st.number_input("Transaction Amount", min_value=0.0, value=5000.0, step=100.0)
            category = st.selectbox("Category", CATEGORIES)
        with second_column:
            merchant = st.text_input("Merchant Name", placeholder="Example: Amazon")
            transaction_date = st.date_input("Transaction Date", value=date.today())

        submitted = st.form_submit_button("Find Best Card", type="primary")

    if submitted:
        if amount <= 0:
            st.error("Transaction amount must be greater than zero.")
        else:
            recommendation = recommend_best_card(cards, amount, category)
            st.session_state["last_recommendation"] = {
                "amount": amount,
                "category": category,
                "merchant": clean_text(merchant) or "Unknown Merchant",
                "transaction_date": transaction_date.isoformat(),
                "result": recommendation,
            }

    saved_result = st.session_state.get("last_recommendation")
    if not saved_result:
        return

    result = saved_result["result"]
    render_recommendation_result(
        result,
        saved_result["amount"],
        saved_result["category"],
        saved_result["merchant"],
    )

    st.subheader("Save Transaction")
    card_options = {
        f"{card['card_name']} ({card['bank_name']})": card
        for card in cards
    }

    with st.form("save_transaction_form"):
        used_card_label = st.selectbox("Which card did you actually use?", list(card_options.keys()))
        used_card = card_options[used_card_label]
        actual_reward = calculate_reward(
            saved_result["amount"],
            get_card_reward_rate(used_card, saved_result["category"]),
        )
        recommended_reward = float(result["highest_benefit"])
        missed_reward = max(0.0, round(recommended_reward - actual_reward, 2))

        st.write(f"Actual reward with selected card: **{format_currency(actual_reward)}**")
        st.write(f"Potential reward missed: **{format_currency(missed_reward)}**")

        save_transaction = st.form_submit_button("Save Transaction", type="primary")

    if save_transaction:
        recommended_card_names = " / ".join([row["card_name"] for row in result["best_cards"]])
        add_transaction(
            {
                "amount": saved_result["amount"],
                "merchant": saved_result["merchant"],
                "category": saved_result["category"],
                "recommended_card": recommended_card_names,
                "used_card": used_card["card_name"],
                "recommended_reward": recommended_reward,
                "actual_reward": actual_reward,
                "missed_reward": missed_reward,
                "transaction_date": saved_result["transaction_date"],
            }
        )
        st.success("Transaction saved successfully.")
        del st.session_state["last_recommendation"]
        rerun_app()


def render_transaction_history() -> None:
    """Transaction history page with filters and sorting."""
    render_header(
        "Transaction History",
        "Review saved recommendations, actual rewards, and missed rewards.",
    )

    dataframe = transactions_to_dataframe(get_transactions())
    if dataframe.empty:
        render_empty_state("No transactions have been saved yet.")
        return

    with st.expander("Filters", expanded=True):
        first_column, second_column, third_column = st.columns(3)
        with first_column:
            date_range = st.date_input(
                "Date Range",
                value=(
                    dataframe["transaction_date"].min().date(),
                    dataframe["transaction_date"].max().date(),
                ),
            )
            category_filter = st.selectbox("Category", ["All"] + sorted(dataframe["category"].unique()))
        with second_column:
            card_names = sorted(
                set(dataframe["used_card"].unique()).union(set(dataframe["recommended_card"].unique()))
            )
            card_filter = st.selectbox("Card", ["All"] + card_names)
            merchant_filter = st.text_input("Merchant contains")
        with third_column:
            sort_option = st.selectbox(
                "Sort By",
                [
                    "Date newest first",
                    "Date oldest first",
                    "Amount highest first",
                    "Amount lowest first",
                    "Reward highest first",
                    "Missed reward highest first",
                ],
            )

    filtered = dataframe.copy()

    if isinstance(date_range, tuple) and len(date_range) == 2:
        start_date, end_date = date_range
        filtered = filtered[
            (filtered["transaction_date"].dt.date >= start_date)
            & (filtered["transaction_date"].dt.date <= end_date)
        ]

    if category_filter != "All":
        filtered = filtered[filtered["category"] == category_filter]

    if card_filter != "All":
        filtered = filtered[
            (filtered["used_card"] == card_filter)
            | (filtered["recommended_card"].str.contains(card_filter, regex=False))
        ]

    if merchant_filter.strip():
        filtered = filtered[
            filtered["merchant"].str.contains(merchant_filter.strip(), case=False, na=False)
        ]

    sort_map = {
        "Date newest first": ("transaction_date", False),
        "Date oldest first": ("transaction_date", True),
        "Amount highest first": ("amount", False),
        "Amount lowest first": ("amount", True),
        "Reward highest first": ("actual_reward", False),
        "Missed reward highest first": ("missed_reward", False),
    }
    sort_column, ascending = sort_map[sort_option]
    filtered = filtered.sort_values(sort_column, ascending=ascending)

    st.dataframe(prepare_transactions_table(filtered), use_container_width=True, hide_index=True)


def render_analytics() -> None:
    """Analytics page with charts and explanatory totals."""
    render_header(
        "Analytics",
        "Understand spending behavior, rewards earned, and reward opportunities missed.",
    )

    dataframe = transactions_to_dataframe(get_transactions())
    if dataframe.empty:
        render_empty_state("Analytics will appear after you save transactions.")
        return

    metric_one, metric_two, metric_three = st.columns(3)
    valuable_card, valuable_reward = most_valuable_card(dataframe)

    metric_one.metric("Most Used Card", most_used_card(dataframe))
    metric_two.metric("Most Valuable Card", valuable_card)
    metric_three.metric("Value Generated", format_currency(valuable_reward))

    missed_current_month = missed_rewards_this_month(dataframe)
    st.info(f"Potential savings missed this month: {format_currency(missed_current_month)}")

    first_column, second_column = st.columns(2)

    with first_column:
        st.subheader("Spending by Category")
        category_data = spending_by_category(dataframe)
        figure = px.bar(
            category_data,
            x="category",
            y="amount",
            color="category",
            color_discrete_sequence=px.colors.qualitative.Set2,
        )
        figure.update_layout(
            xaxis_title="Category",
            yaxis_title="Amount Spent",
            showlegend=False,
            margin=dict(l=10, r=10, t=10, b=10),
        )
        st.plotly_chart(figure, use_container_width=True)

    with second_column:
        st.subheader("Rewards by Credit Card")
        rewards_data = rewards_by_card(dataframe)
        figure = px.bar(
            rewards_data,
            x="used_card",
            y="actual_reward",
            color="used_card",
            color_discrete_sequence=px.colors.qualitative.Pastel,
        )
        figure.update_layout(
            xaxis_title="Card",
            yaxis_title="Rewards Earned",
            showlegend=False,
            margin=dict(l=10, r=10, t=10, b=10),
        )
        st.plotly_chart(figure, use_container_width=True)

    st.subheader("Monthly Spending")
    monthly_data = monthly_spending(dataframe)
    figure = px.line(monthly_data, x="month", y="amount", markers=True)
    figure.update_traces(line_color="#2563eb")
    figure.update_layout(
        xaxis_title="Month",
        yaxis_title="Amount Spent",
        margin=dict(l=10, r=10, t=10, b=10),
    )
    st.plotly_chart(figure, use_container_width=True)

    st.subheader("Most Valuable Existing Card")
    st.markdown(
        f"""
        <div class="soft-panel">
            <div class="card-title">{valuable_card}</div>
            <p style="color: #475569; margin-bottom: 0;">
                This card has generated the highest total actual reward in your saved transaction
                history: <strong>{format_currency(valuable_reward)}</strong>. The calculation adds
                the stored actual reward from every transaction where this card was used.
            </p>
        </div>
        """,
        unsafe_allow_html=True,
    )


def render_settings_about() -> None:
    """Settings and About page."""
    render_header(
        "Settings / About",
        "Load demo data, reset the project, and review the academic project summary.",
    )

    st.subheader("Demo Data")
    st.write(
        "The demo cards are fictional and are included only to demonstrate the recommendation logic."
    )

    demo_column, reset_column = st.columns(2)

    with demo_column:
        if st.button("Load Demo Cards", type="primary"):
            cards_added = load_demo_cards(reset_existing=False)
            if cards_added:
                st.success(f"{cards_added} demo cards were loaded.")
            else:
                st.info("Demo cards are already available.")
            rerun_app()

    with reset_column:
        confirm_reset = st.checkbox("Reset cards and transactions before loading demo cards.")
        if st.button("Reset to Demo Data"):
            if confirm_reset:
                load_demo_cards(reset_existing=True)
                st.success("Database was reset and demo cards were loaded.")
                rerun_app()
            else:
                st.error("Please tick the confirmation box before resetting data.")

    st.subheader("Clear All Project Data")
    confirm_clear = st.checkbox("I understand this will delete all cards and transactions.")
    if st.button("Clear All Data"):
        if confirm_clear:
            reset_database()
            st.success("All project data has been cleared.")
            rerun_app()
        else:
            st.error("Please tick the confirmation box before clearing data.")

    st.subheader("About")
    st.markdown(
        """
        **Smart Card Benefit Recommender** is a Python-based recommendation system
        developed to help credit card users determine the most rewarding card for
        individual transactions.

        **Academic Python Project**

        Technologies used: Python, Streamlit, SQLite, Pandas, and Plotly.
        """
    )


def main() -> None:
    """Application entry point."""
    st.set_page_config(
        page_title="Smart Card Benefit Recommender",
        page_icon="💳",
        layout="wide",
    )
    st.markdown(get_custom_css(), unsafe_allow_html=True)
    initialize_database()

    if LOGO_PATH.exists():
        st.sidebar.image(str(LOGO_PATH), width=86)

    st.sidebar.title("Smart Card")
    st.sidebar.caption("Benefit Recommender")
    selected_page = st.sidebar.radio("Navigation", NAVIGATION_ITEMS)

    if selected_page == "🏠 Dashboard":
        render_dashboard()
    elif selected_page == "✨ Find Best Card":
        render_find_best_card()
    elif selected_page == "💳 My Cards":
        render_my_cards()
    elif selected_page == "➕ Add Card":
        render_add_card()
    elif selected_page == "📊 Analytics":
        render_analytics()
    elif selected_page == "📜 Transaction History":
        render_transaction_history()
    elif selected_page == "⚙️ Settings / About":
        render_settings_about()


if __name__ == "__main__":
    main()
