import os
import hashlib
import importlib.util
import inspect
from jinja2 import Environment, BaseLoader
from .loading import get_loading_files
from .components_compiler import transform_components
from .scripts_type import transform_scripts

# We use BaseLoader for string templates (layouts loaded from files dynamically)
string_env = Environment(loader=BaseLoader())


def load_layout(path_or_file):
    """
    Reads the raw HTML content of a layout file.
    If a Python file path is passed (e.g. __file__), it loads the sibling 'layout.html'.
    Usage in layout.py: return load_layout(__file__)
    """
    if path_or_file.endswith('.py'):
        dir_path = os.path.dirname(path_or_file)
        html_path = os.path.join(dir_path, 'layout.html')
    else:
        html_path = path_or_file

    with open(html_path, 'r', encoding='utf-8') as f:
        return f.read()


def load_layout_module(py_path):
    """
    Dynamically loads the layout.py module if it exists.
    Returns the module object or None.
    """
    if not os.path.exists(py_path):
        return None

    try:
        # Generate a unique name for the module based on path to avoid collisions
        module_name = "layout_" + hashlib.md5(py_path.encode()).hexdigest()
        spec = importlib.util.spec_from_file_location(module_name, py_path)

        if spec and spec.loader:
            module = importlib.util.module_from_spec(spec)
            spec.loader.exec_module(module)
            return module
    except Exception as e:
        print(f"[Caspian] Error loading layout controller {py_path}: {e}")
        raise e

    return None


def find_layouts(route_path):
    """
    Finds all applicable layouts for a given route path.
    Returns a list of base directories where a layout (py or html) exists.
    """
    layouts = []
    # Normalize slashes
    route_path = route_path.replace('\\', '/')
    parts = route_path.split('/')

    # Helper to check existence of layer
    def check_and_add(base_dir):
        # We check for either layout.html OR layout.py
        h_path = os.path.join(base_dir, 'layout.html')
        p_path = os.path.join(base_dir, 'layout.py')
        if os.path.exists(h_path) or os.path.exists(p_path):
            # Store the directory path, not the specific file
            layouts.append(base_dir.replace('\\', '/'))

    # 1. Check for the main Global Root Layout
    check_and_add('src/app')

    # 2. Traverse down the path segments
    current = 'src/app'
    for part in parts:
        if part in ['.', 'src', 'app', ''] or not part:
            continue
        current = os.path.join(current, part)
        check_and_add(current)

    return layouts


def get_layout_id(layout_path):
    """Generates a short hash based on the layout path to serve as an ID."""
    return hashlib.md5(layout_path.encode()).hexdigest()[:8]


def render_with_nested_layouts(children, route_dir, title="App", description="", context_data=None):
    """
    Renders the page content wrapped in layouts.
    - Props bubble up (Child -> Parent).
    - Strict Precedence: If layout.py exists, it MUST return the content.
      HTML is NOT auto-loaded if layout.py exists.
    """
    if context_data is None:
        context_data = {}

    # 1. Transform components/scripts in the page content
    children = transform_components(children, base_dir=route_dir)
    children = transform_scripts(children)

    # 2. Find all layouts
    route_dir = route_dir.replace('\\', '/')
    rel_path = os.path.relpath(route_dir, 'src/app').replace('\\', '/')
    layouts = find_layouts(rel_path)

    current_html = children

    # Identify the Root Layout (index 0) or default to 'none' if no layout exists
    root_layout_id = 'default'
    if layouts:
        root_layout_id = get_layout_id(layouts[0])

    # 3. If no layouts exist
    if not layouts:
        final_html = transform_scripts(current_html + get_loading_files())
        if '<head>' in final_html:
            final_html = final_html.replace(
                '<head>', f'<head>\n<meta name="pp-root-layout" content="{root_layout_id}">')
        return final_html, root_layout_id

    # Initialize accumulator for props bubbling up (Child -> Parent)
    accumulated_props = {}

    # 4. Iterate REVERSED (Leaf -> Root)
    for i in range(len(layouts) - 1, -1, -1):
        layout_dir = layouts[i].replace('/', os.sep)
        layout_html_path = os.path.join(layout_dir, 'layout.html')
        layout_py_path = os.path.join(layout_dir, 'layout.py')

        layer_content = ""
        layer_props = {}

        # A. Check for Python Controller (Strict Precedence)
        has_py = os.path.exists(layout_py_path)

        if has_py:
            module = load_layout_module(layout_py_path)

            if module is not None and hasattr(module, 'layout'):
                try:
                    # Execute layout()
                    sig = inspect.signature(module.layout)
                    if len(sig.parameters) > 0:
                        res = module.layout(context_data)
                    else:
                        res = module.layout()

                    # Handle Return Types
                    if isinstance(res, str):
                        # 1. String -> It is the HTML content
                        layer_content = res
                    elif isinstance(res, tuple) and len(res) == 2:
                        # 2. Tuple -> (HTML, Props)
                        layer_content = res[0]
                        layer_props = res[1]
                    elif isinstance(res, dict):
                        # 3. Dict -> Props ONLY. Content defaults to pass-through.
                        # We do NOT auto-load HTML here to strictly follow Python precedence.
                        layer_props = res
                        layer_content = "{{ children | safe }}"
                    else:
                        # Fallback for None
                        layer_content = "{{ children | safe }}"

                except Exception as e:
                    print(
                        f"[Caspian] Error executing layout() in {layout_py_path}: {e}")
                    raise e
            else:
                # .py exists but no layout() function? Treat as empty pass-through.
                layer_content = "{{ children | safe }}"

        else:
            # B. No Python Controller -> Load HTML directly (if exists)
            if os.path.exists(layout_html_path):
                layer_content = load_layout(layout_html_path)
            else:
                layer_content = "{{ children | safe }}"

        # C. Transform Content (Components + Scripts)
        layer_content = transform_components(
            layer_content, base_dir=layout_dir)
        layer_content = transform_scripts(layer_content)

        # D. Merge Props (Child Overrides Parent)
        merged_props = {**layer_props, **accumulated_props}
        accumulated_props = merged_props

        # E. Handle Root Layout Specifics
        if i == 0:
            current_html += get_loading_files()
            if '<head>' in layer_content:
                layer_content = layer_content.replace(
                    '<head>', f'<head>\n<meta name="pp-root-layout" content="{root_layout_id}">')

        # F. Render Template
        template = string_env.from_string(layer_content)

        layout_dict = {
            'title': title,
            'description': description,
            **merged_props
        }

        render_context = {
            'children': current_html,
            'layout': layout_dict,
            **context_data,
        }

        current_html = template.render(**render_context)

    return transform_scripts(current_html), root_layout_id
