import inspect
import traceback

import random
from math import gcd
from functools import reduce
import sympy as sp
from ._base_subtract_q_gen import BaseSubtractQGen

class SubtractFracQGen(BaseSubtractQGen):
    """
    Subtraction question generator for fractions, with full, step-by-step LaTeX solutions.
    Uses sympy's Rational everywhere for math and LaTeX.
    """

    def __init__(self, min_value=1, max_value=10, max_denominator=12, **kwargs):
        super().__init__(min_value, max_value, **kwargs)
        self.max_denominator = max_denominator

    def _init_swp_templates(self):
        from ._subtract_frac_word_prob_templates import SubtractFracWordProbTemplates
        swp_templates = SubtractFracWordProbTemplates(self)
        return swp_templates
        pass



    def _rand_fraction(self):
        numerator = random.randint(self.min_value, self.max_value)
        denominator = random.randint(2, self.max_denominator)
        return sp.Rational(numerator, denominator)

    def _rand_pair(self):
        a = self._rand_fraction()
        b = self._rand_fraction()
        if a < b:
            a, b = b, a
        return a, b

    def _rand_fractions_and_ops(self, n_terms=2):
        fracs = [self._rand_fraction() for _ in range(n_terms)]
        ops = ['-'] * (n_terms - 1)
        return fracs, ops

    def _lcm_list(self, lst):
        def lcm(a, b):
            return abs(a * b) // gcd(a, b)
        return reduce(lcm, lst, 1)

    def _latex_frac(self, frac):
        return self.sp_latex(frac)

    def _latex_frac_with_mult(self, num, den, mult_num, mult_den):
        return f"\\frac{{{self.sp_latex(num)} \\times {self.sp_latex(mult_num)}}}{{{self.sp_latex(den)} \\times {self.sp_latex(mult_den)}}}"

    def _latex_multi_fraction_solution(self, fracs, ops, **kwargs):
        """
        Returns LaTeX solution as steps (with_steps=True) or as a chained-equality compact answer (with_steps=False).
        Only step/comment lines (e.g. 'Step 1:', etc.) are omitted in compact mode; all calculation lines are preserved.
        """

        with_steps = kwargs.get("with_steps", True)

        try:
            expr = self._latex_frac(fracs[0])
            for op, f in zip(ops, fracs[1:]):
                expr += f" {op} {self._latex_frac(f)}"

            denoms = [f.q for f in fracs]
            L = self._lcm_list(denoms)

            out = []
            # Step 1
            if with_steps:
                out.append(r"\text{Step 1: Set up the numbers:}")
                out.append(r"\\[0.5em]")
            out.append(expr)
            if with_steps:
                out.append(r"\\[1.5em]")

            # Step 2
            if with_steps:
                out.append(f"\\text{{Step 2: Make all denominators equal to their LCM}} = {self.sp_latex(L)}")
                out.append(r"\\[0.5em]")

            eq1 = []
            for frac in fracs:
                mult = L // frac.q
                if mult == 1:
                    eq1.append(f"\\displaystyle  \\frac{{{self.sp_latex(frac.p)}}}{{{self.sp_latex(frac.q)}}}")
                else:
                    eq1.append(self._latex_frac_with_mult(frac.p, frac.q, mult, mult))
            eq1_expr = eq1[0]
            for op, e in zip(ops, eq1[1:]):
                eq1_expr += f" {op} {e}"
            out.append("=" + eq1_expr)
            if with_steps:
                out.append(r"\\[1.5em]")

            # Step 3
            eq2 = []
            for frac in fracs:
                mult = L // frac.q
                n = frac.p * mult
                eq2.append(f"\\displaystyle  \\frac{{{self.sp_latex(n)}}}{{{self.sp_latex(L)}}}")
            eq2_expr = eq2[0]
            for op, e in zip(ops, eq2[1:]):
                eq2_expr += f" {op} {e}"
            out.append("=" + eq2_expr)
            if with_steps:
                out.append(r"\\[1.5em]")

            # Step 4
            combined_num = ""
            sum_num = 0
            for i, frac in enumerate(fracs):
                mult = L // frac.q
                n = frac.p * mult
                if i == 0:
                    combined_num += f"{self.sp_latex(n)}"
                    sum_num += n
                else:
                    op = "+" if ops[i-1] == "+" else "-"
                    combined_num += f" {op} {self.sp_latex(n)}"
                    if op == "+":
                        sum_num += n
                    else:
                        sum_num -= n

            if with_steps:
                out.append(r"\text{Step 5: As we have common denominator for all fractions, we can add all numerators.}")
                out.append(r"\\[0.5em]")
            out.append(f"= \\displaystyle  \\frac{{{combined_num}}}{{{self.sp_latex(L)}}}")
            if with_steps:
                out.append(r"\\[1.5em]")

            # Step 5
            total = fracs[0]
            for op, f in zip(ops, fracs[1:]):
                total = total + f if op == "+" else total - f

            if total.p != sum_num:
                out.append(f"= \\displaystyle  \\frac{{{sum_num}}}{{{self.sp_latex(L)}}}")
                if with_steps:
                    out.append(r"\\[1.5em]")

            out.append("=" + self._latex_frac(total))
            if with_steps:
                out.append(r"\\[1.5em]")

            # Join output appropriately
            if with_steps:
                return "<br> $\\displaystyle " + "\n".join(out) + "$ <br>"
            else:
                # filter out empty or pure latex linebreak/comment lines for compactness
                filtered = [line for line in out if not line.strip().startswith("\\text") and not line.strip().startswith("\\\\")]
                return "<br> $\\displaystyle " + " \\hspace{1em} ".join(filtered) + "$ <br>"

        except Exception as e:
            sError = f"""Error {self.__class__.__name__}.{inspect.stack()[0][3]}: {e}
            fracs = {fracs}
            ops = {ops}
            """
            self._log(sError)
            self._log(traceback.format_exc())
            return None


    def _align_and_subtract_block(self, a, b, **kwargs):
        """
        Returns the multi-step LaTeX solution (as the 4th item), and correct answer (as the 3rd),
        so all question types will display the answer and solution correctly.
        """
        fracs = [a, b]

        op = kwargs.get("op", None)
        if not op is None:
            ops = [op]
        else:
            ops = ['-']

        latex_solution = self._latex_multi_fraction_solution(fracs, ops, **kwargs)
        result = a - b
        result_str = self.sp_latex(result)
        # For backward compatibility: (a_str, b_str, result_str, latex_block/solution)
        return None, None, result_str, latex_solution

    def _result(self, a, b):
        return a - b
