from abc import ABC, abstractmethod
import logging
from jinja2 import Template, StrictUndefined
from pathlib import Path

class SQLFormatter(ABC):
    """Base class for SQL template formatting."""

    def load(self, template_path: str | Path, params: dict | None = None, logger: logging.Logger | None = None, verbose: bool = True) -> str:
        """Load and render a SQL template file.

        Args:
            template_path: Path to SQL template file
            params: Optional dictionary of variables
            logger: Logger instance (uses module logger if None)
            verbose: Whether to log pre/post formatting (default True)

        Returns:
            Rendered SQL string
        """
        if logger is None:
            logger = logging.getLogger(__name__)

        path = Path(template_path)
        if not path.exists():
            raise FileNotFoundError(f"Template file not found: {template_path}")

        with open(path, 'r') as f:
            sql_template = f.read()

        if verbose:
            logger.info(f"Loading SQL template: {template_path}")
            logger.debug(f"Template before formatting:\n\n{sql_template}")

        rendered_sql = self.__class__.render(sql_template, params)

        if verbose:
            logger.info(f"Formatted query:\n\n{rendered_sql}")

        return rendered_sql

    @classmethod
    @abstractmethod
    def render(cls, sql_template: str, params: dict | None = None) -> str:
        """Render SQL template with parameters.

        Args:
            sql_template: SQL string with template syntax
            params: Optional dictionary of variables

        Returns:
            Rendered SQL string
        """
        pass

class PythonStringSQLFormatter(SQLFormatter):
    """Python string format-based SQL formatter."""

    @classmethod
    def render(cls, sql_template: str, params: dict | None = None) -> str:
        """Render SQL template with Python string formatting.

        Args:
            sql_template: SQL string with Python format syntax (e.g., {variable})
            params: Optional dictionary of variables

        Returns:
            Rendered SQL string
        """
        if params is None:
            params = {}

        return sql_template.format(**params)

class JinjaSQLFormatter(SQLFormatter):
    """Jinja2-based SQL formatter."""

    @classmethod
    def render(cls, sql_template: str, params: dict | None = None) -> str:
        """Render SQL template with Jinja2.

        Args:
            sql_template: SQL string with Jinja2 template syntax
            params: Optional dictionary of Jinja variables

        Returns:
            Rendered SQL string
        """
        if params is None:
            params = {}

        template = Template(sql_template, undefined=StrictUndefined)
        return template.render(**params)

def format_query(query: str, params: dict) -> str:
    """Format a query with parameters."""
    return query.format(**params)

def load_query(query_path: str, params: dict | None = None) -> str:
    """Load a query from a file path, optionally formatting with parameters.

    Args:
        query_path: Path to the SQL query file (can be relative or absolute)
        params: Optional dictionary of parameters to format the query with

    Returns:
        The contents of the SQL file as a string, optionally formatted with params
    """
    path = Path(query_path)
    if not path.exists():
        raise FileNotFoundError(f"Query file not found: {query_path}")

    with open(path, 'r') as f:
        query = f.read()

    if params is not None:
        return format_query(query, params)
    else:
        return query
