import pathlib
import logging
import tempfile
from typing import List, Tuple

import json5
import metaphor_python as metaphor
import streamlit as st

from helpers import llm_helper, pptx_helper
from global_config import GlobalConfig


APP_TEXT = json5.loads(open(GlobalConfig.APP_STRINGS_FILE, 'r', encoding='utf-8').read())
GB_CONVERTER = 2 ** 30


logger = logging.getLogger(__name__)


@st.cache_data
def get_contents_wrapper(text: str) -> str:
    """
    Fetch and cache the slide deck contents on a topic by calling an external API.

    :param text: The presentation topic.
    :return: The slide deck contents or outline in JSON format.
    """

    logger.info('LLM call because of cache miss...')
    return llm_helper.generate_slides_content(text).strip()


@st.cache_resource
def get_metaphor_client_wrapper() -> metaphor.Metaphor:
    """
    Create a Metaphor client for semantic Web search.

    :return: Metaphor instance.
    """

    return metaphor.Metaphor(api_key=GlobalConfig.METAPHOR_API_KEY)


@st.cache_data
def get_web_search_results_wrapper(text: str) -> List[Tuple[str, str]]:
    """
    Fetch and cache the Web search results on a given topic.

    :param text: The topic.
    :return: A list of (title, link) tuples.
    """

    results = []
    search_results = get_metaphor_client_wrapper().search(
        text,
        use_autoprompt=True,
        num_results=5
    )

    for a_result in search_results.results:
        results.append((a_result.title, a_result.url))

    return results


def build_ui():
    """
    Display the input elements for content generation. Only covers the first step.
    """

    # get_disk_used_percentage()

    st.title(APP_TEXT['app_name'])
    st.subheader(APP_TEXT['caption'])
    st.markdown(
        'Powered by'
        ' [Mistral-7B-Instruct-v0.2](https://huggingface.co/mistralai/Mistral-7B-Instruct-v0.2).'
    )
    st.markdown(
        '*If the JSON is generated or parsed incorrectly, try again later by making minor changes'
        ' to the input text.*'
    )

    with st.form('my_form'):
        # Topic input
        try:
            with open(GlobalConfig.PRELOAD_DATA_FILE, 'r', encoding='utf-8') as in_file:
                preload_data = json5.loads(in_file.read())
        except (FileExistsError, FileNotFoundError):
            preload_data = {'topic': '', 'audience': ''}

        topic = st.text_area(
            APP_TEXT['input_labels'][0],
            value=preload_data['topic']
        )

        texts = list(GlobalConfig.PPTX_TEMPLATE_FILES.keys())
        captions = [GlobalConfig.PPTX_TEMPLATE_FILES[x]['caption'] for x in texts]

        pptx_template = st.radio(
            'Select a presentation template:',
            texts,
            captions=captions,
            horizontal=True
        )

        st.divider()
        submit = st.form_submit_button('Generate slide deck')

    if submit:
        # st.write(f'Clicked {time.time()}')
        st.session_state.submitted = True

    # https://github.com/streamlit/streamlit/issues/3832#issuecomment-1138994421
    if 'submitted' in st.session_state:
        progress_text = 'Generating the slides...give it a moment'
        progress_bar = st.progress(0, text=progress_text)

        topic_txt = topic.strip()
        generate_presentation(topic_txt, pptx_template, progress_bar)

    st.divider()
    st.text(APP_TEXT['tos'])
    st.text(APP_TEXT['tos2'])

    st.markdown(
        '![Visitors]'
        '(https://api.visitorbadge.io/api/visitors?path=https%3A%2F%2Fhuggingface.co%2Fspaces%2Fbarunsaha%2Fslide-deck-ai&countColor=%23263759)'
    )


def generate_presentation(topic: str, pptx_template: str, progress_bar):
    """
    Process the inputs to generate the slides.

    :param topic: The presentation topic based on which contents are to be generated.
    :param pptx_template: The PowerPoint template name to be used.
    :param progress_bar: Progress bar from the page.
    """

    topic_length = len(topic)
    logger.debug('Input length:: topic: %s', topic_length)

    if topic_length >= 10:
        logger.debug('Topic: %s', topic)
        target_length = min(topic_length, GlobalConfig.LLM_MODEL_MAX_INPUT_LENGTH)

        try:
            # Step 1: Generate the contents in JSON format using an LLM
            json_str = process_slides_contents(topic[:target_length], progress_bar)
            logger.debug('Truncated topic: %s', topic[:target_length])
            logger.debug('Length of JSON: %d', len(json_str))

            # Step 2: Generate the slide deck based on the template specified
            if len(json_str) > 0:
                st.info(
                    'Tip: The generated content doesn\'t look so great?'
                    ' Need alternatives? Just change your description text and try again.',
                    icon="💡️"
                )
            else:
                st.error(
                    'Unfortunately, JSON generation failed, so the next steps would lead'
                    ' to nowhere. Try again or come back later.'
                )
                return

            all_headers = generate_slide_deck(json_str, pptx_template, progress_bar)

            # Step 3: Bonus stuff: Web references and AI art
            show_bonus_stuff(all_headers)

        except ValueError as ve:
            st.error(f'Unfortunately, an error occurred: {ve}! '
                     f'Please change the text, try again later, or report it, sharing your inputs.')

    else:
        st.error('Not enough information provided! Please be little more descriptive :)')


def process_slides_contents(text: str, progress_bar: st.progress) -> str:
    """
    Convert given text into structured data and display. Update the UI.

    :param text: The topic description for the presentation.
    :param progress_bar: Progress bar for this step.
    :return: The contents as a JSON-formatted string.
    """

    json_str = ''

    try:
        logger.info('Calling LLM for content generation on the topic: %s', text)
        json_str = get_contents_wrapper(text)
    except Exception as ex:
        st.error(
            f'An exception occurred while trying to convert to JSON. It could be because of heavy'
            f' traffic or something else. Try doing it again or try again later.'
            f'\nError message: {ex}'
        )

    progress_bar.progress(50, text='Contents generated')

    with st.expander('The generated contents (in JSON format)'):
        st.code(json_str, language='json')

    return json_str


def generate_slide_deck(json_str: str, pptx_template: str, progress_bar) -> List:
    """
    Create a slide deck.

    :param json_str: The contents in JSON format.
    :param pptx_template: The PPTX template name.
    :param progress_bar: Progress bar.
    :return: A list of all slide headers and the title.
    """

    progress_text = 'Creating the slide deck...give it a moment'
    progress_bar.progress(75, text=progress_text)

    # # Get a unique name for the file to save -- use the session ID
    # ctx = st_sr.get_script_run_ctx()
    # session_id = ctx.session_id
    # timestamp = time.time()
    # output_file_name = f'{session_id}_{timestamp}.pptx'

    temp = tempfile.NamedTemporaryFile(delete=False, suffix='.pptx')
    path = pathlib.Path(temp.name)

    logger.info('Creating PPTX file...')
    all_headers = pptx_helper.generate_powerpoint_presentation(
        json_str,
        slides_template=pptx_template,
        output_file_path=path
    )
    progress_bar.progress(100, text='Done!')

    with open(path, 'rb') as f:
        st.download_button('Download PPTX file', f, file_name='Presentation.pptx')

    if temp:
        temp.close()

    return all_headers


def show_bonus_stuff(ppt_headers: List[str]):
    """
    Show bonus stuff for the presentation.

    :param ppt_headers: A list of the slide headings.
    """

    # Use the presentation title and the slide headers to find relevant info online
    logger.info('Calling Metaphor search...')
    ppt_text = ' '.join(ppt_headers)
    search_results = get_web_search_results_wrapper(ppt_text)
    md_text_items = []

    for (title, link) in search_results:
        md_text_items.append(f'[{title}]({link})')

    with st.expander('Related Web references'):
        st.markdown('\n\n'.join(md_text_items))

    logger.info('Done!')

    # # Avoid image generation. It costs time and an API call, so just limit to the text generation.
    # with st.expander('AI-generated image on the presentation topic'):
    #     logger.info('Calling SDXL for image generation...')
    #     # img_empty.write('')
    #     # img_text.write(APP_TEXT['image_info'])
    #     image = get_ai_image_wrapper(ppt_text)
    #
    #     if len(image) > 0:
    #         image = base64.b64decode(image)
    #         st.image(image, caption=ppt_text)
    #         st.info('Tip: Right-click on the image to save it.', icon="💡️")
    #         logger.info('Image added')


def main():
    """
    Trigger application run.
    """

    build_ui()


if __name__ == '__main__':
    main()