# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
# (only), as published by the Free Software Foundation.
#
# 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 Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
# This program is dual-licensed. If you wish to learn more about the
# RhodeCode Enterprise Edition, including its added features, Support services,
# and proprietary license terms, please see https://rhodecode.com/licenses/

import os

import deform
import colander

from rhodecode.translation import _
from rhodecode.model.db import Repository, RepoGroup
from rhodecode.model.validation_schema import validators, preparers


def integration_scope_choices(permissions):
    """
    Return list of (value, label) choices for integration scopes depending on
    the permissions
    """
    result = [('', _('Pick a scope:'))]
    if 'hg.admin' in permissions['global']:
        result.extend([
           ('global', _('Global (all repositories)')),
           ('root-repos', _('Top level repositories only')),
        ])

    repo_choices = [
        ('repo:%s' % repo_name, '/' + repo_name)
        for repo_name, repo_perm
        in list(permissions['repositories'].items())
        if repo_perm == 'repository.admin'
    ]
    repogroup_choices = [
        ('repogroup:%s' % repo_group_name, '/' + repo_group_name + '/ (child repos only)')
        for repo_group_name, repo_group_perm
        in list(permissions['repositories_groups'].items())
        if repo_group_perm == 'group.admin'
    ]
    repogroup_recursive_choices = [
        ('repogroup-recursive:%s' % repo_group_name, '/' + repo_group_name + '/ (recursive)')
        for repo_group_name, repo_group_perm
        in list(permissions['repositories_groups'].items())
        if repo_group_perm == 'group.admin'
    ]
    result.extend(
        sorted(repogroup_recursive_choices + repogroup_choices + repo_choices,
            key=lambda choice_label: choice_label[0].split(':', 1)[1]
        )
    )
    return result


@colander.deferred
def deferred_integration_scopes_validator(node, kw):
    perms = kw.get('permissions')
    def _scope_validator(_node, scope):
        is_super_admin = 'hg.admin' in perms['global']

        if scope.get('repo'):
            if (is_super_admin or perms['repositories'].get(
                scope['repo'].repo_name) == 'repository.admin'):
                return True
            msg = _('Only repo admins can create integrations')
            raise colander.Invalid(_node, msg)
        elif scope.get('repo_group'):
            if (is_super_admin or perms['repositories_groups'].get(
                scope['repo_group'].group_name) == 'group.admin'):
                return True

            msg = _('Only repogroup admins can create integrations')
            raise colander.Invalid(_node, msg)
        else:
            if is_super_admin:
                return True
            msg = _('Only superadmins can create global integrations')
            raise colander.Invalid(_node, msg)

    return _scope_validator


@colander.deferred
def deferred_integration_scopes_widget(node, kw):
    if kw.get('no_scope'):
        return deform.widget.TextInputWidget(readonly=True)

    choices = integration_scope_choices(kw.get('permissions'))
    widget = deform.widget.Select2Widget(values=choices)
    return widget


class IntegrationScopeType(colander.SchemaType):
    def serialize(self, node, appstruct):
        if appstruct is colander.null:
            return colander.null

        if appstruct.get('repo'):
            return 'repo:%s' % appstruct['repo'].repo_name
        elif appstruct.get('repo_group'):
            if appstruct.get('child_repos_only'):
                return 'repogroup:%s' % appstruct['repo_group'].group_name
            else:
                return 'repogroup-recursive:%s' % (
                    appstruct['repo_group'].group_name)
        else:
            if appstruct.get('child_repos_only'):
                return 'root-repos'
            else:
                return 'global'

    def deserialize(self, node, cstruct):
        if cstruct is colander.null:
            return colander.null

        if cstruct.startswith('repo:'):
            repo = Repository.get_by_repo_name(cstruct.split(':')[1])
            if repo:
                return {
                    'repo': repo,
                    'repo_group': None,
                    'child_repos_only': False,
                }
        elif cstruct.startswith('repogroup-recursive:'):
            repo_group = RepoGroup.get_by_group_name(cstruct.split(':')[1])
            if repo_group:
                return {
                    'repo': None,
                    'repo_group': repo_group,
                    'child_repos_only': False
                }
        elif cstruct.startswith('repogroup:'):
            repo_group = RepoGroup.get_by_group_name(cstruct.split(':')[1])
            if repo_group:
                return {
                    'repo': None,
                    'repo_group': repo_group,
                    'child_repos_only': True
                }
        elif cstruct == 'global':
            return {
                'repo': None,
                'repo_group': None,
                'child_repos_only': False
            }
        elif cstruct == 'root-repos':
            return {
                'repo': None,
                'repo_group': None,
                'child_repos_only': True
            }

        raise colander.Invalid(node, '%r is not a valid scope' % cstruct)


class IntegrationOptionsSchemaBase(colander.MappingSchema):

    name = colander.SchemaNode(
        colander.String(),
        description=_('Short name for this integration.'),
        missing=colander.required,
        title=_('Integration name'),
    )

    scope = colander.SchemaNode(
        IntegrationScopeType(),
        description=_(
            'Scope of the integration. Recursive means the integration '
            ' runs on all repos of that group and children recursively.'),
        title=_('Integration scope'),
        validator=deferred_integration_scopes_validator,
        widget=deferred_integration_scopes_widget,
        missing=colander.required,
    )

    enabled = colander.SchemaNode(
        colander.Bool(),
        default=True,
        description=_('Enable or disable this integration.'),
        missing=False,
        title=_('Enabled'),
    )


def make_integration_schema(IntegrationType, settings=None):
    """
    Return a colander schema for an integration type

    :param IntegrationType: the integration type class
    :param settings: existing integration settings dict (optional)
    """

    settings = settings or {}
    settings_schema = IntegrationType(settings=settings).settings_schema()

    class IntegrationSchema(colander.Schema):
        options = IntegrationOptionsSchemaBase()

    schema = IntegrationSchema()
    schema['options'].title = _('General integration options')

    settings_schema.name = 'settings'
    settings_schema.title = _('{integration_type} settings').format(
        integration_type=IntegrationType.display_name)
    schema.add(settings_schema)

    return schema