#!/usr/bin/env python3
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License.  You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied.  See the License for the
# specific language governing permissions and limitations
# under the License.
"""Selfserve Portal for the Apache Software Foundation"""
"""Handler for creating a jira project"""

if not __debug__:
  raise RuntimeError("This code requires assert statements to be enabled")

from ..lib import middleware, asfuid, email, log, config
import quart
import re
import asyncio
import os
import json

RE_VALID_PROJECT_KEY = re.compile(r"^[A-Z0-9]+$")
ACLI_CMD = "/opt/latest-cli/acli.sh"
JIRA_SCHEME_FILES = {
    "workflow": "/x1/acli/site/js/jiraworkflowschemes.json",
}


async def jira_user_exists(username: str):
    """Checks if a jira user exists"""
    proc = await asyncio.create_subprocess_exec(
        ACLI_CMD,
        *("jira", "--action", "getUser", "--userId", username, "--quiet"),
    )
    await proc.wait()
    assert proc.returncode == 0, "Could not find the specified project lead ID in Jira"


async def create_jira_project(
    project_key: str,
    project_name: str,
    description: str,
    project_lead: str,
    issue_scheme: str,
    workflow_scheme: str,
    homepage_url: str,
):
    """Creates a new jira project"""
    assert RE_VALID_PROJECT_KEY.match(project_key), "Invalid project key!"
    proc = await asyncio.create_subprocess_exec(
        ACLI_CMD,
        *(
            "jira",
            "-v",
            "--action",
            "createProject",
            "--project",
            project_key,
            "--name",
            project_name,
            "--description",
            description,
            "--lead",
            project_lead,
            "--issueTypeScheme",
            issue_scheme,
            "--workflowScheme",
            workflow_scheme,
            "--url",
            homepage_url,
            "--notificationScheme",
            "Empty Scheme",
            "--permissionScheme",
            "_Default Permission Scheme_",
        ),
    )
    await proc.wait()
    assert proc.returncode == 0, "Could not create new jira project, it may already exist"


async def set_project_access(project_key: str, ldap_project: str):
    """Sets up default permissions for a project"""
    assert RE_VALID_PROJECT_KEY.match(project_key), "Invalid space name!"
    assert (
        isinstance(ldap_project, str) and ldap_project and ldap_project in config.projects
    ), "Please specify a valid PMC"

    # Admin access for PMC
    proc = await asyncio.create_subprocess_exec(
        ACLI_CMD,
        *(
            "jira",
            "--action",
            "addProjectRoleActors",
            "--project",
            project_key,
            "--role",
            "administrators",
            "--group",
            f"{ldap_project}-pmc",
        ),
    )
    await proc.wait()
    assert proc.returncode == 0, f"Could not assign administrator access to {ldap_project}-pmc"

    # Standard access to project committers
    proc = await asyncio.create_subprocess_exec(
        ACLI_CMD,
        *(
            "jira",
            "--action",
            "addProjectRoleActors",
            "--project",
            project_key,
            "--role",
            "committers",
            "--group",
            ldap_project,
        ),
    )
    await proc.wait()
    assert proc.returncode == 0, f"Could not assign write access to {ldap_project} committers"


@asfuid.session_required
async def process(form_data, session):
    # Create a new jira project

    project_key = form_data.get("project_key")
    project_name = form_data.get("project_name")
    project_lead = form_data.get("project_lead")
    ldap_project = form_data.get("ldap_project")
    issue_scheme = form_data.get("issue_scheme")
    workflow_scheme = form_data.get("workflow_scheme")
    homepage_url = form_data.get("homepage_url")
    description = form_data.get("description")

    try:
        assert (session.pmcs or session.root), "Only members of a (P)PMC may create jira projects"
        assert isinstance(project_key, str) and RE_VALID_PROJECT_KEY.match(project_key), "Invalid project key specified"
        assert isinstance(project_name, str) and project_name, "Please specify a title for the new Jira project"
        assert isinstance(description, str) and description, "Please write a short description of this new project"
        assert isinstance(project_lead, str) and project_lead, "Please specify a project lead for this project"
        assert (
            ldap_project in config.projects
        ), "Please specify a valid, current apache project to assign this Jira project to"
        if not session.root:
            assert ldap_project in session.pmcs, "You can only create a Jira project for an Apache project you are on the PMC of"
        assert isinstance(issue_scheme, str) and issue_scheme, "Please specify a valid issue scheme this project"
        assert (
            isinstance(workflow_scheme, str) and workflow_scheme
        ), "Please specify a valid workflow scheme for this project"
        assert isinstance(homepage_url, str) and homepage_url, "Please specify a homepage URL for this project"

        # Make sure project lead exists in Jira
        await jira_user_exists(project_lead)

        # Set up the new project
        await create_jira_project(
            project_key=project_key,
            project_name=project_name,
            project_lead=project_lead,
            description=description,
            issue_scheme=issue_scheme,
            workflow_scheme=workflow_scheme,
            homepage_url=homepage_url,
        )
        # Set standard access: admin for PMC, read/write for committers
        await set_project_access(project_key, ldap_project)

    except AssertionError as e:
        return {"success": False, "message": str(e)}

    # Notify
    await log.slack(f"A new Jira project, `{project_key}`, has been created as requested by {session.uid}@apache.org.")

    project_email = email.project_to_private(ldap_project)
    email.from_template(
        "jira_project_created.txt",
        recipient=("private@infra.apache.org", project_email, f"{session.uid}@apache.org"),
        variables={
            "project_key": project_key,
            "ldap_project": ldap_project,
            "requester": session.uid,
        },
    )

    # All done for now
    return {
        "success": True,
        "message": "Jira project created",
    }

@asfuid.session_required
async def list_schemes(form_data, session):
    """Lists current valid schemes for Jira"""
    scheme_dict = {}
    for key, filepath in JIRA_SCHEME_FILES.items():
        if os.path.isfile(filepath):
            try:
                js = json.load(open(filepath))
                scheme_dict[key] = js
            except json.JSONDecodeError:  # Bad JSON file? :/
                scheme_dict[key] = {}
    return quart.jsonify(scheme_dict)


quart.current_app.add_url_rule(
    "/api/jira-project-create",
    methods=[
        "POST",  # Create a new jira project
    ],
    view_func=middleware.glued(process),
)


quart.current_app.add_url_rule(
    "/api/jira-project-schemes",
    methods=[
        "GET",  # List valid schemes
    ],
    view_func=middleware.glued(list_schemes),
)
