# 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/>.

from collections import namedtuple

import cherrypy
from cherrypy_foundation.form import CherryForm
from cherrypy_foundation.plugins.ldap import all_attribute
from cherrypy_foundation.tools.i18n import gettext_lazy as _
from wtforms.fields import HiddenField, SelectField, StringField, TextAreaField
from wtforms.validators import InputRequired, Length, Regexp

from bfh_science_ssp.core.const import LNI_COMPUTE
from bfh_science_ssp.core.user import User

Compute = namedtuple('Compute', 'name,permission,usage,owners,group,manage_url,service', defaults=(LNI_COMPUTE,))

SERVER_NAME_REGEXP = r'^[a-zA-Z0-9\-]+$'


class RequestContainerForm(CherryForm):
    """
    Form used to request a new container.
    """

    subject = _('New compute')
    service = "LNI Compute"

    # FIXME Make the list of server type configurable.
    server_type = SelectField(
        label=_('Server type'),
        validators=[
            InputRequired(),
        ],
        choices=[
            ('', _('Select a type…'), {'disabled': 1}),
            ('linux', _('Linux: Debian Server'), {'data-prepend': 'linux'}),
            ('linux-hpc', _('HPC: Debian Server'), {'data-prepend': 'linux'}),
            ('mariadb', _('Database: MariaDB Server'), {'data-prepend': 'mariadb'}),
            ('postgresql', _('Database: PostgreSQL Server'), {'data-prepend': 'postgresql'}),
            ('redis', _('Database: Redis Server'), {'data-prepend': 'redis'}),
            ('git', _('Development: Git Server'), {'data-prepend': 'git'}),
            ('python', _('Linux: Python Server'), {'data-prepend': 'python'}),
            ('python-hpc', _('HPC: Python Server'), {'data-prepend': 'python'}),
            ('r', _('HPC: R Server'), {'data-prepend': 'r'}),
            ('apache', _('Web: Apache Server'), {'data-prepend': 'apache'}),
            ('nodejs', _('Web: Node.js Server'), {'data-prepend': 'nodejs'}),
            ('php', _('Web: PHP Server'), {'data-prepend': 'php'}),
            ('sphinx', _('Web: Sphinx Server'), {'data-prepend': 'sphinx'}),
        ],
        validate_choice=True,
        default='',
    )

    server_usage = SelectField(
        label=_('Server usage'),
        validators=[
            InputRequired(),
        ],
        choices=[('user', _('User (single person)')), ('group', _('Group (multiple persons)'))],
        validate_choice=True,
        default='',
    )

    server_name = HiddenField(
        label=_('Full Server Name'),
    )

    server_user_name = StringField(
        label=_('User server name'),
        validators=[
            InputRequired(),
        ],
        filters=[str.strip],
        description=_('User server names are not editable.'),
        default='',
        render_kw={
            'readonly': '',
            'prepend': '<server-type>.',
            'append': '.bfh.science',
            "data-showif-field": "server_usage",
            "data-showif-operator": "eq",
            "data-showif-value": 'user',
        },
    )

    server_group_name = StringField(
        label=_('Group server name'),
        validators=[
            InputRequired(),
            Length(max=80, message=_('Maximum length of 80.')),
            Regexp(
                SERVER_NAME_REGEXP,
                message=_(
                    'Invalid group name. Only letters, numbers, and dash (-) are allowed. No spaces or special characters.'
                ),
            ),
        ],
        filters=[str.strip],
        default='',
        render_kw={
            'placeholder': "group-name",
            'pattern': SERVER_NAME_REGEXP,
            'title': _('Only letters, numbers, and dash (-) are allowed. No spaces or special characters.'),
            'prepend': '<server-type>.',
            'append': '.bfh.science',
            "data-showif-field": "server_usage",
            "data-showif-operator": "eq",
            "data-showif-value": 'group',
        },
    )

    message = TextAreaField(
        label=_('Additional information'),
        validators=[
            Length(max=500, message=_('Maximum length of 500.')),
        ],
        render_kw={
            "rows": "5",
            "style": "height: 100px",
            'placeholder': _('If anything specific, provide some details.'),
        },
    )

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        # Configure default user server name.
        currentuser = cherrypy.serving.request.currentuser
        self.server_user_name.data = currentuser.username

    def validate(self, extra_validators=None):
        # Lookup prepend value from server_types.
        seleted_prepend = [
            render_kw.get('data-prepend', '')
            for _value, _label, selected, render_kw in self.server_type.iter_choices()
            if selected
        ]
        if not seleted_prepend:
            self.server_type.errors = [_('Invalid server type: %s') % self.server_type.data]
            return False
        server_type_prepend = seleted_prepend[0]

        if self.server_usage.data == 'user':
            currentuser = cherrypy.serving.request.currentuser
            self.server_name.data = f'{server_type_prepend}.{currentuser.username}.bfh.science'
            del self.server_group_name
        elif self.server_usage.data == 'group':
            self.server_name.data = f'{server_type_prepend}.{self.server_group_name.data}.bfh.science'
            del self.server_user_name
        else:
            self.server_usage.errors = [_('Invalid server usage: %s') % self.server_usage.data]
            return False

        self.subject = _('New compute: %s (%s)') % (self.server_name.data, self.server_type.data)

        # Run the real validation with modified fields
        return super().validate(extra_validators)


class ComputeHandler:
    """
    Handler for compute resources.
    """

    @cherrypy.expose()
    @cherrypy.tools.allow(methods=['POST'])
    @cherrypy.tools.ratelimit(method=['POST'])
    def request_container(self, u=None, **kwargs):
        """
        Handle container request by sending email to ticketing system.
        """
        self._send_request(RequestContainerForm())

    @cherrypy.expose()
    @cherrypy.tools.allow(methods=['GET'])
    @cherrypy.tools.json_out()
    def compute_json(self, u=None, **kwargs):
        """
        Return list of compute resources for Datatable.
        """
        # Query user's attributes
        user = self._query__user(u)
        computes = []

        # A user has multiple linux_container, if their user has a non-empty bfhLinuxContainer
        for name in user.linux_containers:
            computes.append(
                Compute(
                    name=name,
                    permission='users',
                    usage='user',
                    owners=[user.as_owner_dict()],
                    group=None,
                    manage_url=None,
                )
            )

        # Read groups name to find compute:
        # cn=IDM.perm.compute.bad9.users,ou=Security,ou=Groups,dc=bfh
        # cn=IDM.perm.<type>.<name>.<permissions>,ou=Security,ou=Groups,dc=bfh
        for group_cn, name, permission in user.deduplicate_groups(
            "cn=IDM.perm.compute.<NAME>.<PERMISSION>,ou=Security,ou=Groups,dc=bfh",
            permission_priority=['admin', 'users'],
        ):

            # Query group details
            groups_dn = [
                f"cn=IDM.perm.compute.{name}.admin,ou=Security,ou=Groups,dc=bfh",
                f"cn=IDM.perm.compute.{name}.users,ou=Security,ou=Groups,dc=bfh",
            ]
            groups_details = User._query_ldap_attributes(
                *groups_dn, attributes=['bfhLinuxContainer', 'bfhGroupManager']
            )
            linux_containers = set()
            for g in groups_details:
                linux_containers.update(all_attribute(g, 'bfhLinuxContainer', default=[]))

            # Query managers.
            managers = User._query_managers(*groups_dn, groups_details=groups_details)
            owners = [manager.as_owner_dict() for manager in managers]

            # Provide manage_url if current user is manager.
            manage_url = None
            currentuser = cherrypy.serving.request.currentuser
            is_owner = any(True for manager in managers if currentuser.username == manager.username)
            manage_url = f"https://idm.bfh.ch/group/{group_cn}" if is_owner else None

            # If LDAP is not properly seeded, the bfhLinuxContainer may be empty.
            # Make sure to create a list one container to represent this groups
            if not linux_containers:
                linux_containers.add(name)

            for name in linux_containers:
                computes.append(
                    Compute(
                        name=name,
                        permission=permission,
                        usage='group',
                        owners=owners,
                        group=group_cn,
                        manage_url=manage_url,
                    )
                )

        # Return a sorted list
        sorted_computes = sorted(computes, key=lambda x: (x.usage != 'user', x.name))
        return {'data': [compute._asdict() for compute in sorted_computes]}
