# 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 base64
import re

import cherrypy
from cherrypy_foundation.form import CherryForm
from cherrypy_foundation.tools.i18n import gettext_lazy as _
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
from wtforms.fields import TextAreaField
from wtforms.validators import InputRequired, Length, ValidationError


class ReplaceSshKeyForm(CherryForm):
    """
    Form used to replace the ssh key.
    """

    subject = _('Replace SSH key')

    ssh_key = TextAreaField(
        label=_('SSH Public Key'),
        validators=[
            InputRequired(),
            Length(max=8192, message=_('Maximum length of 8192.')),
        ],
        filters=[str.strip],
        default='',
        render_kw={
            "rows": "10",
            "style": "height: 250px",
            'placeholder': 'ssh-ed25519 AAAAC3NzaC1lZ...',
        },
    )

    @property
    def accepted_ssh_key_type(self):
        """
        Return list of SSH Key type supported based on application config.
        """
        cfg = cherrypy.tree.apps[''].cfg
        return cfg.accepted_ssh_key_type

    def _convert_putty_to_openssh(self, pubkey):
        """
        Convert the Putty Public Key to OpenSSH Public key.
        """
        # Extract base64 from the public key.
        b64_lines = re.findall(r'^[A-Za-z0-9+/=]+$', pubkey.replace('\r\n', '\n'), re.MULTILINE)
        b64_data = ''.join(b64_lines)
        raw_data = base64.b64decode(b64_data)
        # Get the key type at offset 3
        length = raw_data[3]
        key_type = raw_data[4 : 4 + length].decode()
        return f"{key_type} {b64_data}"

    def validate_ssh_key(self, field):
        """
        Verify if the SSH Key is valid.
        """
        try:
            pubkey_string = field.data.strip()
            # Check if Putty Public Key
            if pubkey_string.startswith('---- BEGIN SSH2 PUBLIC KEY ----'):
                pubkey_string = self._convert_putty_to_openssh(pubkey_string)
            # Chryptographycaly validate the SSH Public Key.
            pubkey = serialization.load_ssh_public_key(pubkey_string.encode(), backend=default_backend())
        except Exception:
            raise ValidationError(message=_('Invalid SSH public key.'))

        # Reformat the SSH Key into OpenSSH Format
        pubkey_string = field.data = pubkey.public_bytes(
            encoding=serialization.Encoding.OpenSSH, format=serialization.PublicFormat.OpenSSH
        ).decode()

        # Verify the SSH key type.
        key_type = pubkey_string.split(' ')[0]
        if self.accepted_ssh_key_type and key_type not in self.accepted_ssh_key_type:
            raise ValidationError(
                message=_('Invalid key type. Only the following SSH key types are accepted: %s')
                % ', '.join(self.accepted_ssh_key_type)
            )


class AccessHandler:
    """
    Handler for Access card.
    """

    @cherrypy.expose()
    @cherrypy.tools.allow(methods=['POST'])
    @cherrypy.tools.ratelimit(method=['POST'])
    def replace_ssh_key(self, u=None, **kwargs):
        """
        Handle SSH Key replace by executing a command.
        """
        # Prohibit ssh key update if disabled.
        user = self._query__user(u)
        if user.ssh_key_status == user.SSH_DISABLED:
            raise cherrypy.HTTPError(400)
        # Update SSH Key.
        cfg = cherrypy.tree.apps[''].cfg
        self._exec_request(cfg.replace_ssh_key_cmd, ReplaceSshKeyForm())
