# app.py
#
# author: deng
# date: 20251127

import threading
from datetime import datetime

import streamlit as st
from utils import convert_image_to_base64, parse_config


def _prewarm_imports() -> None:
    """Pre-import heavy modules in background."""
    import elevation  # noqa: F401 (imports plotly)
    import gpx  # noqa: F401 (imports gpxpy, numpy, geopy)
    import map  # noqa: F401 (imports folium)
    import weather  # noqa: F401


class HikingAssistant:
    def __init__(self, config_path: str = 'assets/config.toml') -> None:
        self._config = parse_config(config_path)

    def _set_page_background(self, image_path: str, opacity: float = 0.8) -> None:
        """Set background image for the application.

        Args:
            image_path: Path to the background image
        """
        image_data = convert_image_to_base64(image_path)
        ext = image_path.split('.')[-1]
        overlay = f'rgba(255, 255, 255, {1 - opacity})'
        st.markdown(
            f"""
            <style>
            .stApp {{
                background-image: linear-gradient({overlay}, {overlay}), url(data:image/{ext};base64,{image_data});
                background-size: cover;
                background-position: center;
                background-attachment: fixed;
            }}
            </style>
            """,
            unsafe_allow_html=True,
        )

    def _add_custom_title(self, title: str, image_path: str) -> None:
        favicon_data = convert_image_to_base64(image_path)
        ext = image_path.split('.')[-1]
        st.markdown(
            f"""
            <h1 style="display: flex; align-items: center; gap: 0.5rem;">
                <img src="data:image/{ext};base64,{favicon_data}" width="48" height="48" style="border-radius: 8px;">
                {title}
            </h1>
            """,
            unsafe_allow_html=True,
        )

    def run(self) -> None:
        """Hiking Assistant application"""
        # Page configuration
        st.set_page_config(
            page_title=self._config['app']['page_title'],
            page_icon=self._config['app']['page_favicon_path'],
            layout='wide',
            initial_sidebar_state='collapsed',
        )
        self._set_page_background(self._config['app']['page_background_path'], self._config['app']['page_background_opacity'])

        # [Block1]
        # Title with favicon
        self._add_custom_title(self._config['app']['page_title'], self._config['app']['page_favicon_path'])

        # File uploader
        uploaded_file = st.file_uploader(
            '上傳您的 GPX 檔案開始分析路線！',
            type=['gpx'],
            help='可以從[健行筆記](https://hiking.biji.co/index.php?q=trail&act=gpx_list)、'
            '[Hikingbook](https://zh-tw.hikingbook.net/explore/trails?regions=Taiwan)等網站下載 GPX 檔案',
        )

        # [Block2]
        # Analysis
        if uploaded_file is not None:
            try:
                # Create cache key based on file name and size
                file_key = f'{uploaded_file.name}_{uploaded_file.size}'

                # Check if file is already processed
                if 'gpx_file_key' not in st.session_state or st.session_state.gpx_file_key != file_key:
                    # Process GPX file (only when it's a new file)
                    with st.spinner('正在解析 GPX 檔案...'):
                        from gpx import GPXProcessor

                        gpx_processor = GPXProcessor(uploaded_file)

                        if not gpx_processor.validate_and_parse():
                            st.error('❌ GPX 檔案格式錯誤或不包含有效的軌跡點')
                            return

                        # Calculate statistics
                        total_distance = gpx_processor.calculate_distance()
                        elevation_gain, elevation_loss = gpx_processor.calculate_elevation_gain_loss()
                        min_elevation, max_elevation = gpx_processor.get_min_max_elevation()
                        start_point, end_point = gpx_processor.get_start_end_points()
                        all_points = gpx_processor.get_all_points()
                        distances, elevations = gpx_processor.get_elevation_profile_data()
                        gradients = gpx_processor.get_gradients()
                        estimated_time = gpx_processor.calculate_naismith_time(
                            horizontal_speed=self._config['app']['estimated_time']['horizontal_speed'],
                            vertical_speed=self._config['app']['estimated_time']['vertical_speed'],
                        )
                        waypoints = gpx_processor.waypoints

                        # Cache all processed data
                        st.session_state.gpx_file_key = file_key
                        st.session_state.total_distance = total_distance
                        st.session_state.elevation_gain = elevation_gain
                        st.session_state.elevation_loss = elevation_loss
                        st.session_state.min_elevation = min_elevation
                        st.session_state.max_elevation = max_elevation
                        st.session_state.start_point = start_point
                        st.session_state.end_point = end_point
                        st.session_state.all_points = all_points
                        st.session_state.distances = distances
                        st.session_state.elevations = elevations
                        st.session_state.gradients = gradients
                        st.session_state.estimated_time = estimated_time
                        st.session_state.waypoints = waypoints

                        # Show altitude sickness warning for first gpx loading
                        if max_elevation >= self._config['app']['altitude_sickness']['elevation_threshold']:
                            st.toast(self._config['app']['altitude_sickness']['warning_text'], icon=self._config['app']['altitude_sickness']['emoji'])

                # Use cached data
                total_distance = st.session_state.total_distance
                elevation_gain = st.session_state.elevation_gain
                elevation_loss = st.session_state.elevation_loss
                min_elevation = st.session_state.min_elevation
                max_elevation = st.session_state.max_elevation
                start_point = st.session_state.start_point
                end_point = st.session_state.end_point
                all_points = st.session_state.all_points
                distances = st.session_state.distances
                elevations = st.session_state.elevations
                gradients = st.session_state.gradients
                estimated_time = st.session_state.estimated_time
                waypoints = st.session_state.waypoints

                # Display statistics section
                st.header('📊 路線五四三')
                col1, col2, col3, col4, col5, col6 = st.columns(6)

                with col1:
                    st.metric(label='總距離', value=f'{total_distance:.1f}km')

                with col2:
                    st.metric(label='總爬升', value=f'{elevation_gain:.0f}m')

                with col3:
                    st.metric(label='總下降', value=f'{elevation_loss:.0f}m')

                with col4:
                    st.metric(label='最高海拔', value=f'{max_elevation:.0f}m')

                with col5:
                    st.metric(label='最低海拔', value=f'{min_elevation:.0f}m')

                with col6:
                    st.metric(
                        label='預估行進時間',
                        value=f'{estimated_time // 60}h {estimated_time % 60}m',
                        help='基於[Naismith’s Rule](https://en.wikipedia.org/wiki/Naismith%27s_rule)修正後計算',
                    )

                # Map section
                with st.spinner('正在渲染地圖...'):
                    from map import MapRenderer
                    from streamlit_folium import st_folium

                    map_renderer = MapRenderer()
                    route_map = map_renderer.create_route_map(all_points, start_point, end_point, waypoints)

                    if route_map:
                        st_folium(route_map, width=None, height=self._config['app']['map']['height'], key='route_map', returned_objects=[])
                    else:
                        st.error('無法渲染地圖')

                # Create two columns: elevation profile on left, pie chart on right
                profile_col, pie_col = st.columns([3, 1])

                with profile_col:
                    with st.spinner('正在繪製海拔剖面圖...'):
                        from elevation import ElevationRenderer

                        profile_renderer = ElevationRenderer()
                        elevation_fig = profile_renderer.create_elevation_profile(distances, elevations, gradients)

                        if elevation_fig:
                            # elevation_fig = profile_renderer.add_gradient_legend(elevation_fig)
                            st.plotly_chart(elevation_fig, width='stretch')
                        else:
                            st.error('無法繪製海拔剖面圖')

                with pie_col:
                    with st.spinner('正在分析坡度分布...'):
                        pie_fig = profile_renderer.create_gradient_pie_chart(distances, gradients)

                        if pie_fig:
                            st.plotly_chart(pie_fig, width='stretch')
                        else:
                            st.error('無法繪製坡度分布圖')

                st.divider()

                # Weather section
                st.header('☀️ 天氣安抓')

                if start_point:
                    from weather import WeatherFetcher

                    weather_fetcher = WeatherFetcher(forecast_days=self._config['app']['weather']['forecast_days'])
                    weather_key = f'weather_{start_point[0]:.6f}_{start_point[1]:.6f}'

                    # Check if weather data is already cached
                    if 'weather_cache_key' not in st.session_state or st.session_state.weather_cache_key != weather_key:
                        # Fetch weather data (only when coordinates change)
                        with st.spinner('正在獲取天氣資訊...'):
                            weather_data = weather_fetcher.get_weather(start_point[0], start_point[1])

                            # Cache weather data
                            st.session_state.weather_cache_key = weather_key
                            st.session_state.weather_data = weather_data
                    else:
                        # Use cached weather data
                        weather_data = st.session_state.weather_data

                    if weather_data:
                        daily_times = weather_data.get('daily_time', [])
                        if daily_times:
                            # Date selector
                            date_labels = [datetime.fromisoformat(d).strftime('%Y/%m/%d') for d in daily_times]
                            selected_date_label = st.selectbox(
                                '出發日期',
                                options=date_labels,
                                index=0,
                                key='weather_date_selector',
                            )
                            selected_index = date_labels.index(selected_date_label)

                            st.subheader('起點天氣')

                            # Get weather data
                            # Current
                            current_humidity = weather_data.get('current_humidity')
                            current_apparent_temperature = weather_data.get('current_apparent_temperature')
                            current_precipitation_probability = weather_data.get('current_precipitation_probability')
                            current_precipitation = weather_data.get('current_precipitation')
                            current_wind_speed = weather_data.get('current_wind_speed')
                            current_wind_direction = weather_data.get('current_wind_direction')
                            # Daily
                            daily_temp_max = weather_data.get('daily_temp_max', [])
                            daily_temp_min = weather_data.get('daily_temp_min', [])
                            daily_apparent_max = weather_data.get('daily_apparent_temp_max', [])
                            daily_apparent_min = weather_data.get('daily_apparent_temp_min', [])
                            daily_mean_relative_humidity = weather_data.get('daily_mean_relative_humidity', [])
                            daily_precip_sum = weather_data.get('daily_precipitation_sum', [])
                            daily_precip_prob = weather_data.get('daily_precipitation_prob', [])
                            daily_wind_speed_max = weather_data.get('daily_wind_speed_max', [])
                            daily_wind_direction_dominant = weather_data.get('daily_wind_direction_dominant', [])
                            daily_sunrise = weather_data.get('daily_sunrise', [])
                            daily_sunset = weather_data.get('daily_sunset', [])
                            daily_uv_index_max = weather_data.get('daily_uv_index_max', [])

                            # Row 1: Temperature (3 columns)
                            col1, col2, col3 = st.columns(3)
                            with col1:
                                st.metric('🔥 最高溫度', f'{daily_temp_max[selected_index]:.1f}°C')
                            with col2:
                                st.metric('❄️ 最低溫度', f'{daily_temp_min[selected_index]:.1f}°C')
                            with col3:
                                if selected_index == 0:
                                    apparent_temp = current_apparent_temperature
                                else:
                                    apparent_temp = (daily_apparent_max[selected_index] + daily_apparent_min[selected_index]) / 2
                                st.metric('🌡️ 體感溫度', f'{apparent_temp:.1f}°C')

                            # Row 2: Humidity, Precipitation, Wind (3 columns)
                            col1, col2, col3 = st.columns(3)
                            with col1:
                                if selected_index == 0:
                                    relative_humidity = current_humidity
                                else:
                                    relative_humidity = daily_mean_relative_humidity[selected_index]
                                st.metric('💧 相對濕度', f'{relative_humidity}%')

                            with col2:
                                if selected_index == 0:
                                    precipitation_probability = current_precipitation_probability
                                    precipitation = current_precipitation
                                else:
                                    precipitation_probability = daily_precip_prob[selected_index]
                                    precipitation = daily_precip_sum[selected_index]
                                st.metric('🌧️ 降雨機率 / 雨量', f'{precipitation_probability}% / {precipitation:.1f}mm')

                            with col3:
                                wind_dir_text = weather_fetcher.convert_wind_degrees_to_flow_direction(
                                    degrees=current_wind_direction if selected_index == 0 else daily_wind_direction_dominant[selected_index]
                                )
                                wind_level = weather_fetcher.get_wind_speed_indicator(
                                    wind_speed=current_wind_speed if selected_index == 0 else daily_wind_speed_max[selected_index]
                                )
                                st.metric('💨 風速 / 流向', f'{daily_wind_speed_max[selected_index]:.1f} km/h {wind_dir_text} {wind_level}')

                            # Row 3: Sunrise, Sunset
                            col1, col2, col3 = st.columns(3)
                            with col1:
                                sunrise_time = datetime.fromisoformat(daily_sunrise[selected_index]).strftime('%H:%M')
                                st.metric('🌅 日出時間', sunrise_time)

                            with col2:
                                sunset_time = datetime.fromisoformat(daily_sunset[selected_index]).strftime('%H:%M')
                                st.metric('🌇 日落時間', sunset_time)

                            with col3:
                                uv_index = daily_uv_index_max[selected_index]
                                uv_level = weather_fetcher.get_uv_index_indicator(uv_index)
                                st.metric('☀️ UV指數', f'{uv_index} {uv_level}')

                            if selected_index >= 3:
                                st.info('❗ 三天後的天氣資訊誤差可能較大')
                        else:
                            st.warning('⚠️ 無法獲取足夠天數的天氣資料')
                    else:
                        st.warning('⚠️ 無法獲取天氣資訊，請稍後再試')
                else:
                    st.warning('⚠️ 無法確定起點位置')

            except Exception as e:
                st.error(f'❌ 處理檔案時發生錯誤: {str(e)}')

        # [Block3]
        # Footer
        st.divider()
        footer_text = (
            f'<div style="text-align:center; font-size: 0.8em">'
            f'<p>{self._config["app"]["page_footer_text"]}<br>v{self._config["app"]["version"]}</p></div>'
        )
        st.markdown(footer_text, unsafe_allow_html=True)


@st.cache_resource(show_spinner=False, max_entries=1)
def get_app_instance() -> HikingAssistant:
    return HikingAssistant()


if __name__ == '__main__':
    threading.Thread(target=_prewarm_imports, daemon=True).start()
    app = get_app_instance()
    app.run()
