#!/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"""
import uuid

"""Handler for jira account creation"""

from ..lib import middleware, config, email
import asfquart
import asyncio
import psycopg

ONE_DAY = 86400  # A day in seconds

# Jira PSQL DSN
JIRA_PGSQL_DSN = psycopg.conninfo.make_conninfo(**config.jirapsql.yaml)

# Mappings dict for userid<->email
JIRA_EMAIL_MAPPINGS = {}

# Reactivation queue. No real need for permanent storage here, all requests can be ephemeral.
JIRA_REACTIVATION_QUEUE = {}

# ACLI command - TODO: Add to yaml??
ACLI_CMD = "/opt/latest-cli/acli.sh"


async def update_jira_email_map():
    """Updates the jira userid<->email mappings from psql on a daily basis"""
    while True:
        print("Updating Jira email mappings dict")
        try:
            tmp_dict = {}
            async with await psycopg.AsyncConnection.connect(JIRA_PGSQL_DSN) as conn:
                async with conn.cursor() as cur:
                    await cur.execute("SELECT lower_user_name, email_address from cwd_user WHERE directory_id != 10000")
                    async for row in cur:
                        if all(x and isinstance(x, str) for x in row):  # Ensure we have actual (non-empty) strings here
                            tmp_dict[row[0]] = row[1]

            # Clear and refresh mappings
            JIRA_EMAIL_MAPPINGS.clear()
            JIRA_EMAIL_MAPPINGS.update(tmp_dict)
        except psycopg.OperationalError as e:
            print(f"Operational error while querying Jira PSQL: {e}")
            print("Retrying later...")
        await asyncio.sleep(ONE_DAY)  # Wait a day...


async def activate_account(username: str):
    """Activates an account through ACLI"""
    email_address = JIRA_EMAIL_MAPPINGS[username]
    proc = await asyncio.create_subprocess_exec(
        ACLI_CMD,
        *(
            "jira",
            "-v",
            "--action",
            "updateUser",
            "--userId",
            username,
            "--userEmail",
            email_address,
            "--activate",
        ),
        stdout=asyncio.subprocess.PIPE,
        stderr=asyncio.subprocess.PIPE,
    )
    stdout, stderr = await proc.communicate()
    if proc.returncode != 0:  # If any errors show up in acli, bork
        # Test for ACLI whining but having done the job due to privacy redactions in Jira (email addresses being blank)
        good_bit = '"active":true'  # If the ACLI JSON output has this, it means the update worked, despite ACLI complaining.
        if good_bit in stdout or good_bit in stderr:
            return  # all good, ignore!
        print(f"Could not reactivate Jira account '{username}': {stderr}")
        raise AssertionError("Jira account reactivation failed due to an internal server error.")


@asfquart.APP.route(
    "/api/jira-account-activate",
    methods=[
        "GET",  # DEBUG
        "POST",  # Account re-activation request from user
    ],
)
async def process_reactivation_request():
    """Initial processing of an account re-activation request:
    - Check that username and email match
    - Send confirmation link to email address
    - Wait for confirmation...
    """
    formdata = await asfquart.utils.formdata()
    session = await asfquart.session.read()
    jira_username = formdata.get("username")
    jira_email = formdata.get("email")
    if jira_email.lower().endswith("@apache.org"):  # This is LDAP operated, don't touch!
        return {"success": False, "message": "Reactivation of internal ASF accounts cannot be done through this tool."}
    if jira_username and jira_username in JIRA_EMAIL_MAPPINGS:
        if JIRA_EMAIL_MAPPINGS[jira_username].lower() == jira_email.lower():  # We have a match!
            # Generate and send confirmation link
            token = str(uuid.uuid4())
            verify_url = f"https://{asfquart.app.request.host}/jira-account-reactivate.html?{token}"
            email.from_template(
                "jira_account_reactivate.txt",
                recipient=jira_email,
                variables={
                    "verify_url": verify_url,
                },
                thread_start=True,
                thread_key=f"jira-activate-{token}",
            )
            # Store marker in our temp dict
            JIRA_REACTIVATION_QUEUE[token] = jira_username
            return {"success": True}
    return {"success": False, "message": "We were unable to find the account based on the information provided. Either your Jira account username, or the email address you registered it with, is incorrect."}


@asfquart.APP.route(
    "/api/jira-account-activate-confirm",
    methods=[
        "GET",  # DEBUG
        "POST",  # Account re-activation request from user
    ],
)
async def process_confirm_reactivation():
    """Processes confirmation link handling (and actual reactivation of an account)"""
    formdata = await asfquart.utils.formdata()
    session = await asfquart.session.read()
    token = formdata.get("token")
    if token and token in JIRA_REACTIVATION_QUEUE:  # Verify token
        username = JIRA_REACTIVATION_QUEUE[token]
        del JIRA_REACTIVATION_QUEUE[token]  # Remove right away, before entering the async wait
        if username in JIRA_EMAIL_MAPPINGS:
            try:
                await activate_account(username)
            except AssertionError as e:
                return {"success": False, "activated": False, "error": str(e)}
            return {"success": True, "activated": True}
    else:
        return {"success": False, "error": "Your token could not be found in our database. Please resubmit your request."}



# Schedule background updater of email mappings
asfquart.APP.add_background_task(update_jira_email_map)
