#!/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.
"""ASF Infrastructure Reporting Dashboard"""
"""Handler for userID availability/syntax checks"""
from ..lib import middleware, config
import os
import yaml
import psycopg
import re
import asyncio
import asfpy.clitools
import asfquart

# Dict of existing users from various canonical sources
existing_users = {
    "jira": [],  # Local Jira accounts
    "confluence": [],  # Local confluence accounts
    "ldap": [],  # LDAP accounts
    "reserved": [],  # Reserved IDs
}

# Valid user ID syntax, defined in config yaml
VALID_USERID_RE = re.compile(config.reporting.userid["valid_userid_syntax"])
SCAN_INTERVAL = 3600  # Scan for changes every one hour


async def scan_for_userids():
    """Scans for all userids in use by the various large systems"""
    while True:
        # LDAP users
        print("Fetching userids from LDAP")
        try:
            ldap_response = await asyncio.wait_for(
                asfpy.clitools.ldapsearch_cli_async(
                    ldap_base="ou=people,dc=apache,dc=org", ldap_scope="sub", ldap_query="uid=*", ldap_attrs=("uid",)
                ),
                120,
            )
            if ldap_response and len(ldap_response) > 1000:
                existing_users["ldap"] = [x["uid"][0] for x in ldap_response]
        except asyncio.exceptions.TimeoutError:
            print("LDAP timed out, retrying later")

        # Jira users, if set up for such (TODO: replace with crowd??)
        if "jirapsql" in config.reporting.userid:
            psql_dsn = psycopg.conninfo.make_conninfo(**config.reporting.userid["jirapsql"])
            print("Fetching local Jira users")
            try:
                temp_list = []
                async with await psycopg.AsyncConnection.connect(psql_dsn) as conn:
                    async with conn.cursor() as cur:
                        await cur.execute("SELECT lower_user_name from cwd_user WHERE directory_id != 10000")
                        async for row in cur:
                            if row[0] and isinstance(row[0], str):  # Ensure only non-empty strings here
                                temp_list.append(row[0])
                # Replace old list with new
                existing_users["jira"] = temp_list
            except psycopg.OperationalError as e:
                print(f"Operational error while querying Jira PSQL: {e}")
                print("Retrying later...")

        # Reserved IDs file
        reserved_ids_filename = config.reporting.userid.get("reserved_ids_file")
        if reserved_ids_filename and os.path.isfile(reserved_ids_filename):
            try:
                reserved_ids = yaml.safe_load(open(reserved_ids_filename))
                existing_users["reserved"] = reserved_ids
            except yaml.YAMLError as e:
                print(f"Could not load {reserved_ids_filename}, skipping: {e}")

        await asyncio.sleep(SCAN_INTERVAL)


@asfquart.APP.route(
    "/api/userid",
)
async def process_userid():
    form_data = await asfquart.utils.formdata()
    userid = form_data.get("id")
    # Check syntax validity
    is_valid = userid and VALID_USERID_RE.match(userid) is not None

    # Check if we already have a user like this
    for group, userlist in existing_users.items():
        if userid in userlist:
            return {
                "checked_id": userid,
                "is_valid": is_valid,
                "exists": True,
                "exists_where": group,
            }

    # No such user, just return validity
    return {
        "checked_id": userid,
        "is_valid": is_valid,
        "exists": False,
        "exists_where": None,
    }



# The userid scan is added as a generic loop. There is no web page for this feature, no need to the plugin registry
asfquart.APP.add_background_task(scan_for_userids)
