# 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 subprocess

import cherrypy
from cherrypy_foundation.flash import flash
from cherrypy_foundation.tools.i18n import gettext_lazy as _
from cherrypy_foundation.url import url_for
from markupsafe import Markup

from bfh_science_ssp.core.user import User

from .access import AccessHandler, ReplaceSshKeyForm
from .cloud import CloudHandler, EnableOpenStackForm
from .common_request import RequestAccessForm, RequestAccessHandler, RequestSupportForm
from .compute import ComputeHandler, RequestContainerForm
from .identity import IdentityHandler
from .infra import InfraHandler
from .storage import CreateStorageForm, RequestStorageAccessChangeForm, RequestStorageAccessForm, StorageHandler

logger = logging.getLogger(__name__)


class DashboardPage(
    StorageHandler, AccessHandler, IdentityHandler, CloudHandler, ComputeHandler, RequestAccessHandler, InfraHandler
):
    """
    Default dashboard page.
    """

    def _query__user(self, username):
        """
        Lookup the user to be displayed.
        """
        currentuser = cherrypy.serving.request.currentuser
        if username and username != currentuser.username:
            # Only Power User are allowed to display other user's ressources.
            if currentuser.ROLE_ADMIN != currentuser.role:
                raise cherrypy.HTTPError(403)
            # Query user's info
            user = User.from_username(username)
            if user is None:
                # Return HTTP error if the user doesn't exists.
                raise cherrypy.HTTPError(404, _('No such user'))
            return user
        return currentuser

    @cherrypy.expose()
    @cherrypy.tools.allow(methods=['GET'])
    @cherrypy.tools.jinja2(template=['dashboard.html'])
    def index(self, u=None, **kwargs):
        """
        Return dashboard page.
        """
        user = self._query__user(u)
        cfg = cherrypy.tree.apps[''].cfg

        return {
            'user': user,
            'change_password_url': cfg.change_password_url,
            'enable_mfa_url': cfg.enable_mfa_url,
            'vpn_info_url': cfg.vpn_info_url,
            'ssh_info_url': cfg.ssh_info_url,
            'wlan_info_url': cfg.wlan_info_url,
            'nextcloud_info_url': cfg.nextcloud_info_url,
            'ssp_info_url': cfg.ssp_info_url,
            'request_access_form': RequestAccessForm(),
            'request_compute_form': RequestContainerForm(),
            'create_storage_form': CreateStorageForm(),
            'request_storage_access_form': RequestStorageAccessForm(),
            'request_storage_access_change_form': RequestStorageAccessChangeForm(),
            'enable_openstack_form': EnableOpenStackForm(),
            'replace_ssh_key_form': ReplaceSshKeyForm(),
            'request_support_form': RequestSupportForm(),
        }

    def _send_request(self, form):
        """
        Reusable method to send form to ticketing system.
        """
        user = cherrypy.serving.request.currentuser
        if form.validate():
            try:
                response = cherrypy.notification.send_request(user, form)
            except Exception as e:
                logger.warning('failed to send request', exc_info=1)
                flash(
                    _(
                        "We couldn't send your request. Please try again later. If it continues, contact the administrator."
                    ),
                    level='error',
                )
                flash(repr(e), level='error')
            else:
                if response:
                    flash(
                        Markup(
                            _(
                                'Success! Your request has been logged as ticket <a href="{ticket_url}" target="_blank">#{ticket_number}</a>. A copy has been emailed to you.'
                            ).format(ticket_number=response.ticket_number, ticket_url=response.ticket_url)
                        ),
                        level='success',
                    )
                else:
                    flash(_("Success! Your request has been sent. A copy has been emailed to you."), level='success')
        else:
            flash(form.error_message, level='error')
        raise cherrypy.HTTPRedirect(url_for('dashboard'))

    def _exec_request(self, cmd, form):
        """
        Reusable method to perform action based on the form by executing a command line.
        """
        # Validate the form command line.
        if not cmd:
            flash(_('This action is currently disabled by the administrator.'), level='warning')
            raise cherrypy.HTTPRedirect(url_for('dashboard'))
        # Validate the form data.
        user = cherrypy.serving.request.currentuser
        if form.validate():
            try:
                # Build the environment with variables.
                env = {
                    "SSP_USER_USERNAME": str(user.username),
                    "SSP_USER_DN": str(user.dn),
                    "SSP_USER_EMAIL": str(user.email),
                }
                # Append form data to environment.
                for field in form:
                    env["SSP_" + str(field.name).upper()] = str(field.data)
                # Execute the command line.
                output = subprocess.run(
                    cmd,
                    env=env,
                    shell=True,
                    check=True,
                    stdin=subprocess.DEVNULL,
                    stdout=subprocess.PIPE,
                    stderr=subprocess.STDOUT,
                    text=True,
                ).stdout
            except subprocess.CalledProcessError as e:
                # TODO We should define a specific error code for "controled" failure vs script failure.
                # e.g.: exit 2 - for warnings stuff - not repported to ITS.
                # On error, we need to send support request to ticketing system.
                logger.warning('failed to execute request', exc_info=1)
                # If the execution of the script failed, let send email.
                form.subject = _("Failure to execute request: ") + form.subject
                form.note = "%s\n%s" % (e, e.stdout)
                response = cherrypy.notification.send_request(user, form)
                flash(
                    Markup(
                        _(
                            'Failed to execute your request. Ticket <a href="{ticket_url}" target="_blank">#{ticket_number}</a> has been created for you and a copy has been emailed to you.<hr />Output: <pre class="mb-0">{output}</pre>'
                        ).format(ticket_number=response.ticket_number, ticket_url=response.ticket_url, output=e.stdout)
                    ),
                    level='error',
                )
            else:
                if output and output.strip():
                    # If the command generate an output. Display a warning
                    flash(output, level='warning')
                else:
                    # Otherwise consider this a success.
                    flash(_("Request executed successfully."), level='success')
        else:
            flash(form.error_message, level='error')
        raise cherrypy.HTTPRedirect(url_for('dashboard'))

    @cherrypy.expose()
    @cherrypy.tools.allow(methods=['GET'])
    @cherrypy.tools.json_out()
    def search_user_json(self, u=None, **kwargs):
        """
        Return list of user matching "u".

        Called by typeahead search.
        """
        users = User.search(u, limit=1000)
        return {
            'status': 200,
            'data': [
                {
                    **user.as_owner_dict(),
                    'url': url_for('dashboard', u=user.username, _relative='server'),
                }
                for user in users
            ],
        }
