import os
from jinja2 import Environment, FileSystemLoader
from besser.BUML.metamodel.gui import GUIModel
from besser.BUML.metamodel.structural import DomainModel
from besser.generators.backend import BackendGenerator
from besser.generators.react import ReactGenerator
from besser.generators import GeneratorInterface

##############################
#   Web Application Generator
##############################
class WebAppGenerator(GeneratorInterface):
    """
    WebAppGenerator is responsible for generating a web application structure based on
    input B-UML and GUI models. It implements the GeneratorInterface and facilitates
    the creation of a web application.

    Args:
        model (DomainModel): The B-UML model representing the application's domain.
        gui_model (GUIModel): The GUI model instance containing necessary configurations.
        output_dir (str, optional): Directory where generated code will be saved. Defaults to None.
    """

    def __init__(self, model: DomainModel, gui_model: GUIModel, output_dir: str = None, agent_model=None, agent_config=None):
        super().__init__(model, output_dir)
        self.gui_model = gui_model
        self.agent_model = agent_model
        self.agent_config = agent_config
        # Jinja environment configuration
        templates_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "templates")
        self.env = Environment(loader=FileSystemLoader(templates_path), trim_blocks=True,
                               lstrip_blocks=True, extensions=['jinja2.ext.do'])

    def generate(self):
        """
        Generates web application code based on the provided B-UML and GUI models.
        If agent_model is provided, also generates agent code.

        Returns:
            None, but store the generated code in the specified output directory.
        """
        self._generate_frontend(self.env)
        self._generate_backend(self.env)
        if self.agent_model:
            self._generate_agent(self.env)
        self._generate_docker_files(self.env)

    def _generate_frontend(self, env):
        # Generate frontend code in 'frontend' subfolder
        frontend_dir = os.path.join(self.output_dir, "frontend") if self.output_dir else "frontend"
        frontend_gen = ReactGenerator(self.model, self.gui_model, output_dir=frontend_dir)
        frontend_gen.generate()

    def _generate_backend(self, env):
        # Generate backend code in 'backend' subfolder
        backend_dir = os.path.join(self.output_dir, "backend") if self.output_dir else "backend"
        backend_gen = BackendGenerator(self.model, output_dir=backend_dir)
        backend_gen.generate()

    def _generate_agent(self, env):
        """Generate agent code if agent model is provided."""
        from besser.generators.agents.baf_generator import BAFGenerator

        # Generate agent code in 'agent' subfolder
        agent_dir = os.path.join(self.output_dir, "agent") if self.output_dir else "agent"
        os.makedirs(agent_dir, exist_ok=True)

        # # Generate agent model file
        # agent_file = os.path.join(agent_dir, "agent_model.py")
        # agent_model_to_code(self.agent_model, agent_file)

        # Generate agent files using BAFGenerator
        agent_gen = BAFGenerator(self.agent_model, output_dir=agent_dir, config=self.agent_config)
        agent_gen.generate()

    def _generate_docker_files(self, env):
        """
        Generates Docker-related files for deployment.

        Args:
            env: The Jinja2 environment for template rendering.
        """
        # Generate docker-compose.yml
        docker_compose_template = env.get_template('docker-compose.yml.j2')
        docker_compose_path = os.path.join(self.output_dir, 'docker-compose.yml')
        with open(docker_compose_path, 'w') as f:
            f.write(docker_compose_template.render(agent_model=self.agent_model, name=self.model.name))

        # Generate frontend Dockerfile
        frontend_dockerfile_template = env.get_template('frontend.Dockerfile.j2')
        frontend_dockerfile_path = os.path.join(self.output_dir, 'frontend', 'Dockerfile')
        os.makedirs(os.path.dirname(frontend_dockerfile_path), exist_ok=True)
        with open(frontend_dockerfile_path, 'w') as f:
            f.write(frontend_dockerfile_template.render())

        # Generate backend Dockerfile
        backend_dockerfile_template = env.get_template('backend.Dockerfile.j2')
        backend_dockerfile_path = os.path.join(self.output_dir, 'backend', 'Dockerfile')
        os.makedirs(os.path.dirname(backend_dockerfile_path), exist_ok=True)
        with open(backend_dockerfile_path, 'w') as f:
            f.write(backend_dockerfile_template.render())

        # Generate agent Dockerfile if agent model exists
        if self.agent_model:
            agent_dockerfile_template = env.get_template('agent.Dockerfile.j2')
            agent_dockerfile_path = os.path.join(self.output_dir, 'agent', 'Dockerfile')
            os.makedirs(os.path.dirname(agent_dockerfile_path), exist_ok=True)
            with open(agent_dockerfile_path, 'w') as f:
                f.write(agent_dockerfile_template.render(agent_model=self.agent_model))
