from pathlib import Path

import librosa
import numpy as np
import pandas as pd
import streamlit as st

from audio_utils_app import (
    SUPPORTED_AUDIO_EXTENSIONS,
    cleanup_temp_files,
    convert_audio_to_wav,
    save_uploaded_file_to_temp,
)
from predict_app import (
    CONFIDENCE_THRESHOLD,
    MODEL_NOT_FOUND_MESSAGE,
    MODEL_PATH,
    get_model_info,
    predict_audio,
)


st.set_page_config(
    page_title="ConfiVoice",
    page_icon="CV",
    layout="wide",
)


SUPPORTED_UPLOAD_TYPES = [
    extension.replace(".", "") for extension in sorted(SUPPORTED_AUDIO_EXTENSIONS)
]


def load_audio_for_plot(wav_path):
    y, sr = librosa.load(wav_path, sr=22050, mono=True)
    if y.size == 0:
        raise ValueError("Audio kosong atau tidak memiliki sinyal suara.")
    return y, sr


def render_waveform(wav_path):
    y, sr = load_audio_for_plot(wav_path)
    max_points = 3000
    step = max(1, len(y) // max_points)
    y_plot = y[::step]
    time_axis = np.arange(len(y_plot)) * step / sr

    waveform = pd.DataFrame(
        {
            "Waktu (detik)": time_axis,
            "Amplitudo": y_plot,
        }
    ).set_index("Waktu (detik)")

    st.subheader("Waveform")
    st.line_chart(waveform, height=260)


def render_spectrogram(wav_path):
    y, sr = load_audio_for_plot(wav_path)
    spectrogram = librosa.amplitude_to_db(np.abs(librosa.stft(y)), ref=np.max)
    min_value = float(np.min(spectrogram))
    max_value = float(np.max(spectrogram))

    if max_value > min_value:
        spectrogram_image = (spectrogram - min_value) / (max_value - min_value)
    else:
        spectrogram_image = np.zeros_like(spectrogram)

    st.subheader("Spectrogram")
    st.image(
        np.flipud(spectrogram_image),
        caption="Frekuensi rendah di bawah, frekuensi tinggi di atas.",
        use_container_width=True,
        clamp=True,
    )


def render_audio_uploader(key):
    return st.file_uploader(
        "Upload audio",
        type=SUPPORTED_UPLOAD_TYPES,
        key=key,
    )


def prepare_uploaded_audio(uploaded_file):
    temp_input_path = save_uploaded_file_to_temp(uploaded_file)
    temp_wav_path = convert_audio_to_wav(temp_input_path)
    return temp_input_path, temp_wav_path


def render_prediction_result(result):
    if result.get("is_valid_audio") is False:
        st.subheader("Audio Tidak Valid")
        st.error(result.get("error_message") or result.get("explanation") or "Audio tidak valid.")
        with st.expander("Kualitas audio"):
            st.json(result.get("audio_quality", {}))
        return

    probability_pd = result["probabilities"]["PD"]
    probability_tpd = result["probabilities"]["TPD"]
    confidence = result["confidence"]

    st.subheader("Hasil Prediksi")
    metric_cols = st.columns(3)
    metric_cols[0].metric("Label", result["label"])
    metric_cols[1].metric("Keterangan", result["description"])
    metric_cols[2].metric("Confidence", f"{confidence * 100:.2f}%")

    st.progress(probability_pd, text=f"Probabilitas PD: {probability_pd * 100:.2f}%")
    st.progress(probability_tpd, text=f"Probabilitas TPD: {probability_tpd * 100:.2f}%")

    if confidence < CONFIDENCE_THRESHOLD:
        st.warning("Model belum yakin. Confidence di bawah 60%, pertimbangkan untuk merekam ulang audio.")
    else:
        st.success(f"Hasil utama: {result['label']} - {result['description']}")

    if result.get("explanation"):
        st.info(result["explanation"])

    with st.expander("Kualitas audio dan indikator suara"):
        st.write("Kualitas audio")
        st.json(result.get("audio_quality", {}))
        st.write("Indikator suara")
        st.json(result.get("voice_indicators", {}))

    with st.expander("Output dictionary"):
        st.json(result)


def render_upload_preview(uploaded_file, show_visuals=True):
    temp_input_path = None
    temp_wav_path = None

    try:
        temp_input_path, temp_wav_path = prepare_uploaded_audio(uploaded_file)
        st.audio(uploaded_file.getvalue(), format=uploaded_file.type or "audio/wav")

        if show_visuals:
            waveform_col, spectrogram_col = st.columns(2)
            with waveform_col:
                render_waveform(temp_wav_path)
            with spectrogram_col:
                render_spectrogram(temp_wav_path)

        return temp_input_path
    except Exception as error:
        st.error(f"Gagal memproses audio: {error}")
        return None
    finally:
        cleanup_temp_files(temp_wav_path)


def show_home():
    st.title("ConfiVoice")
    st.write("Aplikasi Streamlit untuk memprediksi suara PD atau TPD dari model machine learning.")

    st.info(
        "Folder aplikasi ini hanya membaca model dari `../ml/models/` dan fitur dari `../ml/features.py`."
    )

    st.write("Menu yang tersedia:")
    st.write("- Beranda")
    st.write("- Prediksi Suara")
    st.write("- Visualisasi Audio")
    st.write("- Informasi Model")
    st.write("- Tentang Sistem")


def show_prediction():
    st.title("Prediksi Suara")
    st.write("Upload audio dalam format WAV, MP3, M4A, OGG, FLAC, WEBM, atau AAC.")

    uploaded_file = render_audio_uploader("prediction_upload")
    if uploaded_file is None:
        return

    temp_input_path = None
    temp_wav_path = None

    try:
        temp_input_path, temp_wav_path = prepare_uploaded_audio(uploaded_file)
        st.audio(uploaded_file.getvalue(), format=uploaded_file.type or "audio/wav")

        waveform_col, spectrogram_col = st.columns(2)
        with waveform_col:
            render_waveform(temp_wav_path)
        with spectrogram_col:
            render_spectrogram(temp_wav_path)

        if st.button("Prediksi", type="primary"):
            with st.spinner("Mengekstraksi fitur dan menjalankan model..."):
                result = predict_audio(temp_input_path)
            render_prediction_result(result)
    except FileNotFoundError as error:
        message = MODEL_NOT_FOUND_MESSAGE if str(error) == MODEL_NOT_FOUND_MESSAGE else str(error)
        st.error(message)
    except Exception as error:
        st.error(f"Gagal memproses prediksi: {error}")
    finally:
        cleanup_temp_files(temp_input_path, temp_wav_path)


def show_visualization():
    st.title("Visualisasi Audio")
    uploaded_file = render_audio_uploader("visualization_upload")
    if uploaded_file is None:
        return

    temp_input_path = None
    temp_wav_path = None

    try:
        temp_input_path, temp_wav_path = prepare_uploaded_audio(uploaded_file)
        st.audio(uploaded_file.getvalue(), format=uploaded_file.type or "audio/wav")
        render_waveform(temp_wav_path)
        render_spectrogram(temp_wav_path)
    except Exception as error:
        st.error(f"Gagal membuat visualisasi: {error}")
    finally:
        cleanup_temp_files(temp_input_path, temp_wav_path)


def show_model_info():
    st.title("Informasi Model")

    try:
        info = get_model_info()
    except FileNotFoundError:
        st.error(MODEL_NOT_FOUND_MESSAGE)
        return
    except Exception as error:
        st.error(f"Gagal membaca informasi model: {error}")
        return

    st.write(f"Path model: `{info['model_path']}`")
    st.write(f"Tipe model: `{info['model_type']}`")
    st.write(f"Class model: `{', '.join(info['classes'])}`")
    st.write(f"Jumlah fitur yang diharapkan: `{info['expected_features']}`")
    st.write(f"Model utama: `{MODEL_PATH.relative_to(MODEL_PATH.parents[2])}`")


def show_about():
    st.title("Tentang Sistem")
    st.write(
        "ConfiVoice memisahkan aplikasi Streamlit dari proses machine learning. "
        "Folder `cv_app/` berisi antarmuka aplikasi, sementara folder `ml/` tetap menjadi tempat dataset, training, fitur audio, dan model."
    )
    st.write(
        "Audio upload dikonversi sementara ke WAV mono 22050 Hz, lalu fitur diekstraksi memakai `../ml/features.py`. "
        "Tidak ada audio upload yang disimpan permanen ke `ml/data/`."
    )


MENU_HANDLERS = {
    "Beranda": show_home,
    "Prediksi Suara": show_prediction,
    "Visualisasi Audio": show_visualization,
    "Informasi Model": show_model_info,
    "Tentang Sistem": show_about,
}


selected_menu = st.sidebar.radio("Menu", list(MENU_HANDLERS.keys()))
MENU_HANDLERS[selected_menu]()
