"""
Apple Accelerate Sparse Solver Interface
"""

from emerge_aasds import AccelerateInterface, Factorization, Symmetry
import time


class AASDSInterface:
    """
    Apple Accelerate Sparse Solver Interface
    
    Uses Apple's native highly-optimized sparse direct solver
    
    Usage:
        ctx = AccelerateInterface()
        ctx.analyse(A)
        ctx.factorize(A)
        x, info = ctx.solve(b)
    
    Parameters
    ----------
    factorization : str, optional
        Factorization type: 'lu' (default, works for complex), 'qr', 'cholesky', 'ldlt'
    symmetry : str, optional
        Matrix symmetry: 'nonsymmetric' (default), 'symmetric', 'hermitian'
        Note: For complex symmetric matrices (A=A^T, not A=A^H), use 'nonsymmetric'
              Accelerate's 'symmetric' mode may assume A=A^H for complex
    verbose : int, optional
        Verbosity level (0=silent, 1=timing info)
    
    Recommended settings for EM problems:
        - Complex symmetric: factorization='lu', symmetry='nonsymmetric'
        - Real SPD: factorization='cholesky', symmetry='symmetric'
    """
    
    def __init__(self, verbose=0):
        self._solver = None
        self._factorization = Factorization.LU
        self._csym: bool = True
        
        self._factored = False
        self.verbose = verbose
    
    @property
    def _symmetry(self) -> Symmetry:
        if self._csym:
            return Symmetry.SYMMETRIC
        else:
            return Symmetry.NONSYMMETRIC
        
    def analyse(self, A):
        """
        Symbolic factorization
        
        Parameters
        ----------
        A : scipy.sparse matrix
            Sparse matrix to analyze
        """
        t0 = time.time()
        self._solver = AccelerateInterface(
            factorization=self._factorization,
            symmetry=self._symmetry,
            verbose=0  # We handle our own verbosity
        )
        self._solver.analyse(A)
        
        if self.verbose > 0:
            print(f"Analyse: {time.time()-t0:.3f}s")
        
        self._factored = False
    
    def factorize(self, A):
        """
        Numeric factorization
        
        Parameters
        ----------
        A : scipy.sparse matrix
            Matrix with same sparsity pattern as analyse()
        """
        if self._solver is None:
            raise RuntimeError("Call analyse() first")
        
        t0 = time.time()
        
        try:
            self._solver.factorize(A)
        except Exception as e:
            # Factorization failed - provide helpful error message
            error_msg = f"Factorization failed with {self._factorization}: {e}"
            
            if self._factorization == 'ldlt':
                error_msg += "\n  Hint: LDLT requires symmetric indefinite matrices."
                error_msg += "\n        For complex symmetric EM problems, try 'qr' instead."
            elif self._factorization == 'cholesky':
                error_msg += "\n  Hint: Cholesky requires symmetric POSITIVE DEFINITE matrices."
                error_msg += "\n        For indefinite matrices, try 'ldlt' or 'qr'."
            elif self._factorization == 'lu':
                error_msg += "\n  Hint: LU factorization failed - matrix may be singular."
                error_msg += "\n        Try 'qr' for more robust factorization."
            
            raise RuntimeError(error_msg)
        
        if self.verbose > 0:
            print(f"factorize: {time.time()-t0:.3f}s")
        
        self._factored = True
    
    def solve(self, b):
        """
        Solve system using factorization
        
        Parameters
        ----------
        b : numpy.ndarray
            Right-hand side vector or matrix
        
        Returns
        -------
        x : numpy.ndarray
            Solution vector or matrix
        info : dict or None
            Solver info (for compatibility with MUMPS interface)
        """
        if not self._factored:
            raise RuntimeError("Call factorize() first")
        
        t0 = time.time()
        x, info = self._solver.solve(b)
        
        if self.verbose > 0:
            print(f"Run: {time.time()-t0:.3f}s")
        
        return x, info
    
    def destroy(self):
        """Cleanup solver resources"""
        if self._solver is not None:
            self._solver.destroy()
            self._solver = None
        self._factored = False
    
    def __del__(self):
        self.destroy()
    
    def __repr__(self):
        status = "factored" if self._factored else "not factored"
        return f"AccelerateInterface(factorization='{self._factorization}', symmetry='{self._csym}', {status})"


# Convenience function matching SPOOLES pattern
def create_accelerate_interface(factorization='lu', symmetry='nonsymmetric', verbose=0):
    """
    Create Accelerate solver interface
    
    Parameters
    ----------
    factorization : str, optional
        'lu' (default, works for complex general matrices)
        'qr' (robust but may crash on complex matrices in macOS 26)
        'cholesky' (symmetric positive definite only, real matrices)
        'ldlt' (symmetric indefinite, real matrices only)
    
    symmetry : str, optional
        'nonsymmetric' (default, safest for complex matrices)
        'symmetric' (A = A^T, use for real symmetric only)
        'hermitian' (A = A^H, for complex Hermitian)
    
    verbose : int, optional
        0 = silent, 1 = timing info
    
    Returns
    -------
    solver : AccelerateInterface
        Configured solver instance
    
    Examples
    --------
    For complex symmetric EM problems (RECOMMENDED):
    >>> solver = create_accelerate_interface('lu', symmetry='nonsymmetric', verbose=1)
    >>> solver.analyse(A)
    >>> solver.factorize(A)
    >>> x, info = solver.solve(b)
    
    For real symmetric positive definite (fastest):
    >>> solver = create_accelerate_interface('cholesky', symmetry='symmetric', verbose=1)
    
    Notes
    -----
    - LU is most reliable for complex matrices on macOS 26
    - QR/LDLT may crash on complex matrices (Apple Accelerate limitation)
    - For complex symmetric (A=A^T), use symmetry='nonsymmetric' to avoid issues
    """
    return AASDSInterface(factorization=factorization, symmetry=symmetry, verbose=verbose)
