import json
import dash
from dash.dependencies import Input, Output, State, ALL
from dash import dcc, html
import dash_bootstrap_components as dbc
import asyncio
import aiohttp
from concurrent.futures import ThreadPoolExecutor, as_completed

# Local modules
from app.services.exam_set import prob_i
from app.models.model import generate_prob, get_problem_content
#from app.services.exercise import Exercise
#cmp = Exercise()
from app.services.complete import Complete
cmp = Complete()

# Lists for handling specific logic:
# - skip_ls => if problem_index is in skip_ls, skip multiple-choice and simply show question + submit
# - n_ls => if problem_index is in n_ls, append "①..⑤" lines at the end of question_part
skip_ls = ['41_42', '43_45']
n_ls = ['25', '29', '30', '35', '38', '39']

async def generate_exam(data):
    """
    Async version for batch exam creation if needed.
    """
    text = data.get('text', '')
    suneung_output = [None] * 26

    async with aiohttp.ClientSession() as async_sess:
        tasks = []
        for i in range(25):
            c = cmp.prompt(prob_i(i))
            if c:
                tasks.append(generate_prob(i % 15, c, text, i + 1))
        completed = await asyncio.gather(*tasks)
        for (result, idx) in completed:
            suneung_output[idx] = result
    return suneung_output

def generate_exam_(data):
    """
    ThreadPool version for batch exam creation if needed.
    """
    text = data.get('text', '')
    suneung_output = [None] * 26
    with ThreadPoolExecutor() as executor:
        futures = []
        for i in range(25):
            c = cmp.prompt(prob_i(i))
            if c:
                futures.append(executor.submit(generate_prob, i % 15, c, text, i + 1))
        for future in as_completed(futures):
            result, idx = future.result()
            suneung_output[idx] = result
    return suneung_output

def callbacks(app):
    """
    Registers callbacks for:
      1) show_suneung_layout: toggles container for '수능' button.
      2) display_problem: triggered by problem dropdown => loads question,
         uses skip_ls/n_ls logic, returns an HTML block with question lines,
         multiple-choice if needed, submit button, and hidden stores.
         Also handles modal creation. 
         'allow_duplicate=True' is used because we also update 'problem-display'
         in on_modal_ok callback.
      3) check_answer_open_modal: triggered by 'submit-problem-button' => 
         compares chosen radio vs correct answer => updates modal only 
         (no spinner if 'problem-display' is not updated).
      4) on_modal_ok: if header == "Correct", replace 'problem-display' with 
         the entire full_text, else close modal. Also uses allow_duplicate=True.
    """

    @app.callback(
        Output('suneung-container', 'style'),
        Input('exercise-suneung', 'n_clicks'),
        prevent_initial_call=True
    )
    def show_suneung_layout(n):
        """
        Reveals the 'suneung-container' if user clicks the '수능' button.
        Otherwise remains hidden.
        """
        if n and n > 0:
            return {'display': 'block', 'margin-top': '10px'}
        return {'display': 'none'}

    @app.callback(
        [
            # 'loading-dummy' => we set "Loading..." at start => spinner on, then "" => off
            Output('loading-dummy', 'children'),
            # 'problem-display' => actual question and controls
            Output('problem-display', 'children', allow_duplicate=True),
        ],
        Input({'type': 'problem-item', 'index': ALL}, 'n_clicks'),
        State('textarea', 'value'),
        prevent_initial_call=True
    )
    def display_problem(n_clicks_list, value):
        """
        Called when user selects a problem from the dropdown.
        1) 'loading-dummy' is initially set to "Loading..." => spinner triggers
        2) After building the question content, set 'loading-dummy' = "" => spinner ends
        3) Also return the question HTML in 'problem-display'.
        skip_ls => skip multiple-choice
        n_ls => add lines "①..⑤"
        """
        ctx = dash.callback_context
        if not ctx.triggered or all(n == 0 for n in n_clicks_list):
            return ("", "No problem selected.")

        # Start spinner
        dummy_text = "Loading..."

        trig_id = ctx.triggered[0]['prop_id']
        item_id = json.loads(trig_id.split('.')[0])
        problem_index = item_id['index']

        full_text = get_problem_content(problem_index, value)
        if not full_text:
            # End spinner => ""
            return ("", "No problem content.")

        splitted = full_text.split('정답: ')
        question_part = splitted[0]
        answer_part = ''
        if len(splitted) > 1:
            answer_part = splitted[1].strip()

        if problem_index in n_ls:
            question_part += "\n①\n②\n③\n④\n⑤\n"

        lines = question_part.split('\n')
        question_lines, radio_options = [], []
        for ln in lines:
            ln_s = ln.strip()
            if ln_s.startswith(('①', '②', '③', '④', '⑤')):
                radio_options.append(ln_s)
            else:
                question_lines.append(ln_s)

        question_div = html.Div(
            '\n'.join(question_lines),
            className='serif',
            style={'white-space': 'pre-wrap'}
        )

        hidden_fulltext = dcc.Store(id='hidden-full-text', data=full_text)
        hidden_answer = dcc.Store(id='hidden-answer-store', data=answer_part)

        modal = dbc.Modal(
            [
                dbc.ModalHeader(
                    id='explanation-modal-header',
                    className='opaque-modal-content',
                    style={
                        'padding': '0rem 2rem',
                        'height': '4rem',
                        'background-color': '#299dcb'
                    }
                ),
                dbc.ModalBody(
                    id='explanation-modal-body',
                    className='opaque-modal-content'
                ),
                dbc.ModalFooter(
                    dbc.Button(
                        'OK',
                        id='explanation-modal-ok',
                        n_clicks=0,
                        className='btn btn-secondary'
                    ),
                    style={'padding': '0.5rem 0.5rem'},
                    className='opaque-modal-content'
                )
            ],
            id='explanation-modal',
            is_open=False,
            backdrop='static',
            keyboard=False,
            centered=True
        )

        if problem_index in skip_ls:
            # Skip multiple-choice
            submit_btn = dbc.Button(
                'Submit',
                id='submit-problem-button',
                n_clicks=0,
                className='btn btn-sm btn-outline-dark sans',
                style={'width': '10rem', 'background-color': '#299dcb'}
            )
            final_div = html.Div([
                question_div,
                html.Br(),
                submit_btn,
                modal,
                hidden_fulltext,
                hidden_answer
            ], style={'white-space': 'pre-wrap'})

            # End spinner => dummy_text=''
            return ("", final_div)

        if not radio_options:
            # No multiple choice found => just show text
            return ("", html.Div([
                question_div,
                html.Br(),
                "(No multiple-choice found.)"
            ]))

        radio_comp = dcc.RadioItems(
            id='problem-radioitems',
            options=[{'label': opt, 'value': opt} for opt in radio_options],
            value='',
            labelStyle={'display': 'block'},
            inputStyle={'margin-right': '6px'}
        )

        submit_btn = dbc.Button(
            'Submit',
            id='submit-problem-button',
            n_clicks=0,
            className='btn btn-sm btn-outline-dark sans',
            style={'width': '10rem', 'background-color': '#299dcb'}
        )

        final_div = html.Div([
            question_div,
            radio_comp,
            html.Br(),
            submit_btn,
            modal,
            hidden_fulltext,
            hidden_answer
        ], style={'white-space': 'pre-wrap'})

        # End spinner => dummy_text=''
        return ("", final_div)

    @app.callback(
        [
            Output('explanation-modal', 'is_open', allow_duplicate=True),
            Output('explanation-modal-header', 'children', allow_duplicate=True),
            Output('explanation-modal-body', 'children', allow_duplicate=True)
        ],
        Input('submit-problem-button', 'n_clicks'),
        [
            State('problem-radioitems', 'value'),
            State('hidden-answer-store', 'data'),
            State('explanation-modal', 'is_open')
        ],
        prevent_initial_call=True
    )
    def check_answer_open_modal(n_clicks, chosen_val, ans_part, modal_open):
        """
        Triggered by the Submit button:
          - Compare chosen radio value vs. the correct answer
          - Update only the modal => no spinner if 'problem-display' or 'loading-dummy' not changed
        """
        if n_clicks < 1:
            return (modal_open, dash.no_update, dash.no_update)

        if not chosen_val:
            return (
                True,
                "Incorrect",
                "No option selected. Please pick an answer."
            )

        num_map = {'①': '1', '②': '2', '③': '3', '④': '4', '⑤': '5'}

        chosen_mark = chosen_val.strip()[:1]
        chosen_mark = num_map.get(chosen_mark, None)

        ans_str = ans_part.strip() if ans_part else ''
        a_char = ans_str[:1] if ans_str else ''
        a_char = num_map.get(a_char, None)

        if chosen_mark and a_char and (chosen_mark == a_char):
            return (True, "Correct", "Congratulations! Click OK to see the explanation.")
        else:
            return (True, "Incorrect", "That is not the correct answer. Try again.")

    @app.callback(
        [
            Output('problem-display', 'children', allow_duplicate=True),
            Output('explanation-modal', 'is_open', allow_duplicate=True)
        ],
        Input('explanation-modal-ok', 'n_clicks'),
        [
            State('explanation-modal-header', 'children'),
            State('hidden-full-text', 'data'),
            State('explanation-modal', 'is_open')
        ],
        prevent_initial_call=True
    )
    def on_modal_ok(n_ok, modal_header, full_text, modal_open):
        """
        If modal_header == "Correct", replace 'problem-display' with full_text. 
        Otherwise, just close the modal. 
        No spinner => we do not update 'loading-dummy' here.
        """
        if n_ok < 1:
            return (dash.no_update, modal_open)

        if modal_header == "Correct":
            replaced_div = html.Div(
                full_text,
                className='mx-0 mb-3 py-1 serif',
                style={'white-space': 'pre-wrap'}
            )
            return (replaced_div, False)

        return (dash.no_update, False)
