# BFH Science Self-Service Portal
# Copyright (C) 2025-2026 IKUS Software
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <https://www.gnu.org/licenses/>.

import logging

import cherrypy
import openstack
from cherrypy.process.plugins import SimplePlugin

logger = logging.getLogger(__name__)


class OpenStackError(Exception):
    pass


class OpenStackPlugin(SimplePlugin):
    """
    Plugin to query openstack using command line interface.
    """

    username = None
    domain = "default"
    password = None
    auth_url = None

    _conn = None
    _domains = {}

    def start(self):
        # Don't start if URL is not defined.
        if not self.auth_url:
            return
        self.bus.log('Start OpenStack plugin')
        try:
            # Connect to openstack
            self._conn = openstack.connect(
                auth_url=self.auth_url,
                project_name='admin',
                project_domain_name=self.domain,
                username=self.username,
                password=self.password,
                user_domain_name=self.domain,
                load_yaml_config=False,
                load_envvars=False,
            )
            # Collect domain names
            self._domains = {d.name: d.id for d in self._conn.identity.domains()}

            # Collect roles names
            self._roles = {role.id: role.name for role in self._conn.identity.roles()}
        except Exception:
            logger.exception('fail to connect to openstack API')

    def stop(self):
        self.bus.log('Stop OpenStack plugin')

    def _get_role_name(self, role_id):
        """
        Get name of the role.
        """
        role_name = self._roles.get(role_id)
        if not role_name:
            # Refresh the role names list
            self._roles = {role.id: role.name for role in self._conn.identity.roles()}
        return self._roles.get(role_id, None)

    def get_server_list(self, project_id):
        """
        Query server list from project.
        """
        if not self._conn:
            return
        for server in self._conn.compute.servers(project_id=project_id, all_projects=True):
            yield {
                'id': server.id,
                'name': server.name,
                'cpu': server.flavor.vcpus,
                'ram': server.flavor.ram,
                'disk': server.flavor.disk,
            }

    def get_project_list(self, username):
        """
        Query all project "accessible" for the given username within *any* domain.
        """
        if not self._conn:
            return
        # 1. Search for username in every known domain.
        for domain_id in self._domains.values():
            user = self._conn.identity.find_user(username, domain_id=domain_id)
            if not user:
                continue
            # 2. List projects accessible for that user
            for assignment in self._conn.identity.role_assignments(user_id=user.id):
                project_id = assignment.scope.get('project', {}).get('id', None)
                role_id = assignment.role.get('id', None)
                if project_id is None or role_id is None:
                    logger.warning('project_id or role_id is undefined for role assignment %s', assignment)
                    continue
                role_name = self._get_role_name(role_id)
                # 3. Get quota for each project.
                compute_quota = self._conn.get_compute_quotas(project_id)
                volume_quota = self._conn.get_volume_quotas(project_id)
                # 3. Get project information.
                project = self._conn.identity.get_project(project_id)
                yield {
                    'id': project.id,
                    'name': project.name,
                    'role_id': role_id,
                    'role_name': role_name,
                    'cpu': compute_quota.cores,
                    'ram': compute_quota.ram,
                    'disk': volume_quota.gigabytes,
                }


cherrypy.openstack = OpenStackPlugin(cherrypy.engine)
cherrypy.openstack.subscribe()

cherrypy.config.namespaces['openstack'] = lambda key, value: setattr(cherrypy.openstack, key, value)
