# 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.
#
#
# Deploys a standalone ferretdb relying on postgres db
#

import kopf, json, time
import nuvolaris.kube as kube
import nuvolaris.kustomize as kus
import nuvolaris.config as cfg
import nuvolaris.util as util
import nuvolaris.postgres_operator as postgres
import logging
import nuvolaris.openwhisk as openwhisk
import urllib.parse
import nuvolaris.operator_util as operator_util

from nuvolaris.user_config import UserConfig
from nuvolaris.user_metadata import UserMetadata

def enrich_ferretdb_data(data):
     data['size']= cfg.get('mongodb.volume-size') or 10
     data['dir']= "/state"            
     data['storageClass']=cfg.get("nuvolaris.storageclass")
     data['name']="nuvolaris-mongodb"
     data['container']="ferretdb"
     data['applypodsecurity']=util.get_enable_pod_security()
     
     # use nuvolaris postgres db as default configuration for FERRETDB
     data['ferretdb_postgres_url'] = postgres.get_base_postgres_url(data)
     util.ferretb_affinity_tolerations_data(data)

def create(owner=None):
    """
    Deploys ferret db and wait for the pod to be ready.
    """
    logging.info("*** creating ferretdb")
    
    data = util.get_postgres_config_data()
    enrich_ferretdb_data(data)

    tplp = ["security-set-attach.yaml","set-attach.yaml","ferretdb-sts.yaml"]
    if(data['affinity'] or data['tolerations']):
       tplp.append("affinity-tolerance-sts-core-attach.yaml")
           
    mkust = kus.patchTemplates("ferretdb", tplp, data)    
    mspec = kus.kustom_list("ferretdb", mkust, templates=[], data=data)

    if owner:
        kopf.append_owner_reference(mspec['items'], owner)
    else:
        cfg.put("state.ferretdb.spec", mspec)
    
    res = kube.apply(mspec)

    # dynamically detect ferretdb pod and wait for readiness
    if res:
        util.wait_for_pod_ready("{.items[?(@.metadata.labels.app == 'nuvolaris-mongodb')].metadata.name}")
        update_system_cm_for_mdb(data)
        logging.info("*** created ferretdb")
        
    return res

def get_ferret_pod_name():
    return util.get_pod_name("{.items[?(@.metadata.labels.app == 'nuvolaris-mongodb')].metadata.name}")    

def update_system_cm_for_mdb(data):
    logging.info("*** annotating configuration for ferretdb nuvolaris user")
    try:        
        mdb_service = util.get_service("{.items[?(@.metadata.name == 'nuvolaris-mongodb-svc')]}")
        if(mdb_service):
            mdb_pod_name = get_ferret_pod_name()        
            mdb_service_name = mdb_service['metadata']['name']            
            mdb_ns = mdb_service['metadata']['namespace']

            data = util.get_postgres_config_data()

            username = urllib.parse.quote(data['postgres_nuvolaris_user'])
            password = urllib.parse.quote(data['postgres_nuvolaris_password'])
            auth = f"{username}:{password}"
            
            mdb_url = f"mongodb://{auth}@{mdb_pod_name}.{mdb_service_name}.{mdb_ns}.svc.cluster.local:27017/nuvolaris?connectTimeoutMS=60000&authMechanism=PLAIN"
            openwhisk.annotate(f"mongodb_url={mdb_url}")
            logging.info("*** saved annotation for ferretdb nuvolaris user")            
    except Exception as e:
        logging.error(f"failed to build mongodb_url for nuvolaris database: {e}")  

def delete_by_owner():
    spec = kus.build("ferretdb")
    res = kube.delete(spec)
    logging.info(f"delete ferretdb: {res}")
    return res

def delete_by_spec():
    spec = cfg.get("state.ferretdb.spec")
    res = False
    if spec:
        res = kube.delete(spec)
        logging.info(f"delete ferretdb: {res}")
    
    return res

def delete(owner=None):
    if owner:        
        return delete_by_owner()
    else:
        return delete_by_spec()

def patch(status, action, owner=None):
    """
    Called the the operator patcher to create/delete ferredb
    """
    try:
        logging.info(f"*** handling request to {action} ferretdb")  
        if  action == 'create':
            msg = create(owner)
            operator_util.patch_operator_status(status,'ferretdb','on')
        else:
            msg = delete(owner)
            operator_util.patch_operator_status(status,'ferretdb','off')

        logging.info(msg)        
        logging.info(f"*** handled request to {action} ferretdb") 
    except Exception as e:
        logging.error('*** failed to update ferretdb: %s' % e)
        operator_util.patch_operator_status(status,'ferretdb','error')            

def _add_mdb_user_metadata(user_metadata, data):
    """
    adds an entry for the mongodb connectivity, i.e
    something like "mongodb://{namespace}:{auth}@nuvolaris-mongodb-0.nuvolaris-mongodb-svc.nuvolaris.svc.cluster.local:27017/{database}?connectTimeoutMS=60000"}
    """ 
    try:
        mdb_service = util.get_service("{.items[?(@.metadata.name == 'nuvolaris-mongodb-svc')]}")        

        if(mdb_service):
            mdb_service_name = mdb_service['metadata']['name']            
            mdb_ns = mdb_service['metadata']['namespace']
            mdb_pod_name = get_ferret_pod_name()

            username = urllib.parse.quote(data["username"])
            password = urllib.parse.quote(data["password"])
            auth = f"{username}:{password}"            
            database = data["database"]

            mdb_url = f"mongodb://{auth}@{mdb_pod_name}.{mdb_service_name}.{mdb_ns}.svc.cluster.local:27017/{database}?connectTimeoutMS=60000&authMechanism=PLAIN"
            user_metadata.add_metadata("MONGODB_URL",mdb_url)
        return None
    except Exception as e:
        logging.error(f"failed to build mongodb_url for {data['database']}: {e}")
        return None

def create_db_user(ucfg: UserConfig, user_metadata: UserMetadata):
    database = ucfg.get('mongodb.database')
    subject = ucfg.get('namespace')
    namespace = ucfg.get('namespace')
    logging.info(f"authorizing new ferretdb database {database}")

    try:
        data = util.get_postgres_config_data()
        data["database"]=f"{database}_ferretdb"
        data["username"]=f"{subject}_ferretdb"
        data["password"]=ucfg.get('mongodb.password')
        data["mode"]="create"
        
        path_to_pgpass = postgres.render_postgres_script(f"{namespace}_ferretdb","pgpass_tpl.properties",data)
        path_to_mdb_script = postgres.render_postgres_script(f"{namespace}_ferretdb","postgres_manage_user_tpl.sql",data)
        pod_name = util.get_pod_name("{.items[?(@.metadata.labels.app == 'nuvolaris-postgres')].metadata.name}")      

        if(pod_name):
            res = postgres.exec_psql_command(pod_name,path_to_mdb_script,path_to_pgpass)

            if res:
                _add_mdb_user_metadata(user_metadata, data)
                return res
            else:
                logging.error("failed to execute SQL script on Postgres DB to enable MongoDB (FerretDB) user")

        return None
    except Exception as e:
        logging.error(f"failed to add Mongodb database {database}: {e}")
        return None

def delete_db_user(namespace, database):
    logging.info(f"removing ferretdb database {database}")

    try:
        data = util.get_postgres_config_data()        
        data["database"]=f"{database}_ferretdb"
        data["username"]=f"{namespace}_ferretdb"
        data["mode"]="delete"

        path_to_pgpass = postgres.render_postgres_script(f"{namespace}_ferretdb","pgpass_tpl.properties",data)
        path_to_mdb_script = postgres.render_postgres_script(f"{namespace}_ferretdb","postgres_manage_user_tpl.sql",data)
        pod_name = util.get_pod_name("{.items[?(@.metadata.labels.app == 'nuvolaris-postgres')].metadata.name}")

        if(pod_name):
            res = postgres.exec_psql_command(pod_name,path_to_mdb_script,path_to_pgpass)
            return res 

        return None
    except Exception as e:
        logging.error(f"failed to remove Ferretdb database {namespace} authorization id and key: {e}")
        return None        