<filename>api/proposal_api.py
import pprint
from typing import List, Dict

import fastapi
import httpx
from pathlib import Path
from fastapi import Depends, Header
from fastapi.responses import JSONResponse
from fastapi.security import APIKeyHeader
from pymongo import MongoClient

from infrastucture import settings
from models.proposal import ProposalIn, ProposalUpdate
from models.facility import Cycle
from api.facility_api import facility_data

from services import pass_service

client = MongoClient(settings.NSLS2CORE_MONGODB_URI)
# client = motor.motor_asyncio.AsyncIOMotorClient(settings.NSLS2CORE_MONGODB_URI)

router = fastapi.APIRouter()


@router.get('/proposals/{cycle}')
async def get_proposals_for_cycle(cycle: str):
    database = client["nsls2core"]
    collection = database["cycles"]
    query = {"name": str(cycle)}
    projection = {"proposals": 1.0, "_id": 0.0}
    cursor = collection.find(query, projection=projection)
    result = []
    for doc in cursor:
        result.append(doc)
    return result


# @router.get('/proposals')
# def get_all_proposals():
#     pass


@router.get('/proposal/{proposal_id}/usernames')
async def get_proposal_usernames(proposal_id: ProposalIn = Depends(), return_json: bool=True):
    database = client["nsls2core"]
    collection = database["proposals"]

    pipeline = [
        {
            "$match": {
                "proposal_id": str(proposal_id.proposal_id)
            }
        },
        {
            "$project": {
                "users": 1.0
            }
        },
        {
            "$group": {
                "_id": None,
                "usernames": {
                    "$addToSet": "$users.username"
                }
            }
        },
        {
            "$addFields": {
                "usernames": {
                    "$filter": {
                        "input": {
                            "$first": u"$usernames"
                        },
                        "as": "un",
                        "cond": {
                            "$ne": [
                                "$$un",
                                None
                            ]
                        }
                    }
                }
            }
        }
    ]

    cursor = collection.aggregate(
        pipeline,
        allowDiskUse=False
    )

    result = {"usernames": []}
    for doc in cursor:
        result["usernames"].extend(doc["usernames"])

    if return_json:
        return JSONResponse(content=result)

    return result


@router.get('/proposal/{proposal_id}/users')
async def get_proposal_users(proposal_id: ProposalIn = Depends()):
    database = client["nsls2core"]
    collection = database["proposals"]
    query = {"proposal_id": str(proposal_id.proposal_id)}
    projection = {"users": 1.0, "proposal_id": True, "_id": 0.0}
    proposal_doc = collection.find_one(query, projection=projection)
    if proposal_doc is None:
        return {'error_message': f"No proposal {str(proposal_id.proposal_id)} found."}
    return JSONResponse(content=proposal_doc)


@router.get('/proposal/{proposal_id}/directories')
async def get_proposal_directories(proposal_id: ProposalIn = Depends(), testing: bool = False):
    database = client["nsls2core"]
    collection = database["proposals"]

    # First get the proposal details from the database
    query = {"proposal_id": str(proposal_id.proposal_id)}
    projection = {"_id": 0.0, "last_updated": 0.0}
    proposal_doc = collection.find_one(query, projection=projection)

    if proposal_doc is None:
        return {'error_message': f"No proposal {str(proposal_id.proposal_id)} found."}

    data_session = proposal_doc['data_session']
    beamlines = proposal_doc['instruments']
    cycles = proposal_doc['cycles']

    # if any of the above are null or zero length, then we don't have
    # enough information to create any directories
    insufficient_information = False
    error_msg = []

    if data_session is None:
        insufficient_information = True
        error_msg.append(f"Proposal {str(proposal_id.proposal_id)} does not contain a data_session.")

    if len(cycles) == 0:
        insufficient_information = True
        error_msg.append(f"Proposal {str(proposal_id.proposal_id)} does not contain any cycle information.")

    if len(beamlines) == 0:
        insufficient_information = True
        error_msg.append(f"Proposal {str(proposal_id.proposal_id)} does not contain any beamlines.")

    directories = []

    if testing:
        pprint.pprint(f"Testing is TRUE {testing}")
        root = Path('/nsls2/data/.testing')
    else:
        pprint.pprint(f"Testing is FALSE {testing}")
        root = Path('/nsls2/data')

    for beamline in beamlines:
        for cycle in cycles:
            beamline_tla = str(beamline).lower()
            users_acl: list[dict[str, str]] = []
            groups_acl: list[dict[str, str]]  = []

            users_acl.append({'nsls2data': 'rw'})
            groups_acl.append({str(data_session):"rw"})

            groups_acl.append({'n2sn-right-dataadmin': "rw"})
            groups_acl.append({f"n2sn-right-dataadmin-{beamline_tla}": "rw"})

            directory = {'path': root / beamline_tla / 'proposals' / str(cycle) / str(data_session),
                         'owner': 'nsls2data', 'group': str(data_session), 'group_writable' : True,
                         'users': users_acl, 'groups': groups_acl}
            directories.append(directory)

    if insufficient_information:
        response = {'error_message': error_msg}
        return response

    return directories


@router.get('/proposal/{proposal_id}/group-membership')
async def get_proposal_groups(proposal_id: ProposalIn = Depends()):
    database = client["nsls2core"]
    collection = database["proposals"]

    # First get the proposal details from the database
    query = {"proposal_id": str(proposal_id.proposal_id)}
    projection = {"_id": 0.0, "last_updated": 0.0}
    proposal_doc = collection.find_one(query, projection=projection)

    if proposal_doc is None:
        return {'error_message': f"No proposal {str(proposal_id.proposal_id)} found."}

    data_session = proposal_doc['data_session']

    if data_session is None:
        return {'error_message': f"No data session found for proposal {str(proposal_id.proposal_id)}."}

    usernames = await get_proposal_usernames(proposal_id, return_json=False)

    response = {}

    response['groupname'] = str(data_session)
    response['usernames'] = usernames['usernames']

    return response


@router.get('/proposal/{proposal_id}')
async def get_proposal_from_pass(proposal_id: ProposalIn = Depends()):
    database = client["nsls2core"]
    collection = database["proposals"]
    query = {"proposal_id": str(proposal_id.proposal_id)}
    projection = {"_id": 0.0, "last_updated": 0.0}
    proposal_doc = collection.find_one(query, projection=projection)
    if proposal_doc is None:
        return {'error_message': f"No proposal {str(proposal_id.proposal_id)} found."}
    return JSONResponse(content=proposal_doc)


@router.get('/pass/proposal/{proposal_id}')
async def get_proposal_from_pass(proposal_id: ProposalIn = Depends()):
    proposal = await pass_service.get_proposal(proposal_id.proposal_id)
    return proposal


# @router.post('/proposal/{proposal_id}')


@router.put('/proposal/{proposal_id}')
async def update_proposal(proposal: ProposalUpdate = Depends()):
    facility_info = facility_data[proposal.facility.name]
    msg = {
        'message': f'I am new going to update my local information for Proposal ID {proposal.proposal_id} for the '
                   f'{facility_info["pass_facility_id"]} facility.'}
    return msg


@router.get('/saf/{proposal_id}')
async def get_proposal(proposal_id: ProposalIn = Depends()):
    saf = await pass_service.get_saf_from_proposal(proposal_id.proposal_id)
    return saf

# URL = "http://n2snadmin.nsls2.bnl.gov:5000"
# client_to_windows = httpx.AsyncClient(base_url=URL, headers={"X-API-KEY": n2sn_service.api_key})


# X-API-KEY
# "gu-999999-9"

# @router.post("/proposal/{proposal_id}")
# async def post_proposal(proposal_id: ProposalIn):
#     await client_to_windows.post("admin/group", json={"name": proposal_id.proposal_id})

<filename>api/proposal_api.py
import pprint
from typing import List, Dict

import fastapi
import httpx
from pathlib import Path
from fastapi import Depends, Header
from fastapi.responses import JSONResponse
from fastapi.security import APIKeyHeader
from pymongo import MongoClient

from infrastucture import settings
from models.proposal import ProposalIn, ProposalUpdate
from models.facility import Cycle
from api.facility_api import facility_data

from services import pass_service

client = MongoClient(settings.NSLS2CORE_MONGODB_URI)
# client = motor.motor_asyncio.AsyncIOMotorClient(settings.NSLS2CORE_MONGODB_URI)

router = fastapi.APIRouter()


@router.get('/proposals/{cycle}')
async def get_proposals_for_cycle(cycle: str):
    database = client["nsls2core"]
    collection = database["cycles"]
    query = {"name": str(cycle)}
    projection = {"proposals": 1.0, "_id": 0.0}
    cursor = collection.find(query, projection=projection)
    result = []
    for doc in cursor:
        result.append(doc)
    return result


# @router.get('/proposals')
# def get_all_proposals():
#     pass


@router.get('/proposal/{proposal_id}/usernames')
async def get_proposal_usernames(proposal_id: ProposalIn = Depends(), return_json: bool=True):
    database = client["nsls2core"]
    collection = database["proposals"]

    pipeline = [
        {
            "$match": {
                "proposal_id": str(proposal_id.proposal_id)
            }
        },
        {
            "$project": {
                "users": 1.0
            }
        },
        {
            "$group": {
                "_id": None,
                "usernames": {
                    "$addToSet": "$users.username"
                }
            }
        },
        {
            "$addFields": {
                "usernames": {
                    "$filter": {
                        "input": {
                            "$first": u"$usernames"
                        },
                        "as": "un",
                        "cond": {
                            "$ne": [
                                "$$un",
                                None
                            ]
                        }
                    }
                }
            }
        }
    ]

    cursor = collection.aggregate(
        pipeline,
        allowDiskUse=False
    )

    result = {"usernames": []}
    for doc in cursor:
        result["usernames"].extend(doc["usernames"])

    if return_json:
        return JSONResponse(content=result)

    return result


@router.get('/proposal/{proposal_id}/users')
async def get_proposal_users(proposal_id: ProposalIn = Depends()):
    database = client["nsls2core"]
    collection = database["proposals"]
    query = {"proposal_id": str(proposal_id.proposal_id)}
    projection = {"users": 1.0, "proposal_id": True, "_id": 0.0}
    proposal_doc = collection.find_one(query, projection=projection)
    if proposal_doc is None:
        return {'error_message': f"No proposal {str(proposal_id.proposal_id)} found."}
    return JSONResponse(content=proposal_doc)


@router.get('/proposal/{proposal_id}/directories')
async def get_proposal_directories(proposal_id: ProposalIn = Depends(), testing: bool = False):
    database = client["nsls2core"]
    collection = database["proposals"]

    # First get the proposal details from the database
    query = {"proposal_id": str(proposal_id.proposal_id)}
    projection = {"_id": 0.0, "last_updated": 0.0}
    proposal_doc = collection.find_one(query, projection=projection)

    if proposal_doc is None:
        return {'error_message': f"No proposal {str(proposal_id.proposal_id)} found."}

    data_session = proposal_doc['data_session']
    beamlines = proposal_doc['instruments']
    cycles = proposal_doc['cycles']

    # if any of the above are null or zero length, then we don't have
    # enough information to create any directories
    insufficient_information = False
    error_msg = []

    if data_session is None:
        insufficient_information = True
        error_msg.append(f"Proposal {str(proposal_id.proposal_id)} does not contain a data_session.")

    if len(cycles) == 0:
        insufficient_information = True
        error_msg.append(f"Proposal {str(proposal_id.proposal_id)} does not contain any cycle information.")

    if len(beamlines) == 0:
        insufficient_information = True
        error_msg.append(f"Proposal {str(proposal_id.proposal_id)} does not contain any beamlines.")

    directories = []

    if testing:
        pprint.pprint(f"Testing is TRUE {testing}")
        root = Path('/nsls2/data/.testing')
    else:
        pprint.pprint(f"Testing is FALSE {testing}")
        root = Path('/nsls2/data')

    for beamline in beamlines:
        for cycle in cycles:
            beamline_tla = str(beamline).lower()
            users_acl: list[dict[str, str]] = []
            groups_acl: list[dict[str, str]]  = []

            users_acl.append({'nsls2data': 'rw'})
            groups_acl.append({str(data_session):"rw"})

            groups_acl.append({'n2sn-right-dataadmin': "rw"})
            groups_acl.append({f"n2sn-right-dataadmin-{beamline_tla}": "rw"})

            directory = {'path': root / beamline_tla / 'proposals' / str(cycle) / str(data_session),
                         'owner': 'nsls2data', 'group': str(data_session), 'group_writable' : True,
                         'users': users_acl, 'groups': groups_acl}
            directories.append(directory)

    if insufficient_information:
        response = {'error_message': error_msg}
        return response

    return directories


@router.get('/proposal/{proposal_id}/group-membership')
async def get_proposal_groups(proposal_id: ProposalIn = Depends()):
    database = client["nsls2core"]
    collection = database["proposals"]

    # First get the proposal details from the database
    query = {"proposal_id": str(proposal_id.proposal_id)}
    projection = {"_id": 0.0, "last_updated": 0.0}
    proposal_doc = collection.find_one(query, projection=projection)

    if proposal_doc is None:
        return {'error_message': f"No proposal {str(proposal_id.proposal_id)} found."}

    data_session = proposal_doc['data_session']

    if data_session is None:
        return {'error_message': f"No data session found for proposal {str(proposal_id.proposal_id)}."}

    usernames = await get_proposal_usernames(proposal_id, return_json=False)

    response = {}

    response['groupname'] = str(data_session)
    response['usernames'] = usernames['usernames']

    return response


@router.get('/proposal/{proposal_id}')
async def get_proposal_from_pass(proposal_id: ProposalIn = Depends()):
    database = client["nsls2core"]
    collection = database["proposals"]
    query = {"proposal_id": str(proposal_id.proposal_id)}
    projection = {"_id": 0.0, "last_updated": 0.0}
    proposal_doc = collection.find_one(query, projection=projection)
    if proposal_doc is None:
        return {'error_message': f"No proposal {str(proposal_id.proposal_id)} found."}
    return JSONResponse(content=proposal_doc)


@router.get('/pass/proposal/{proposal_id}')
async def get_proposal_from_pass(proposal_id: ProposalIn = Depends()):
    proposal = await pass_service.get_proposal(proposal_id.proposal_id)
    return proposal


# @router.post('/proposal/{proposal_id}')


@router.put('/proposal/{proposal_id}')
async def update_proposal(proposal: ProposalUpdate = Depends()):
    facility_info = facility_data[proposal.facility.name]
    msg = {
        'message': f'I am new going to update my local information for Proposal ID {proposal.proposal_id} for the '
                   f'{facility_info["pass_facility_id"]} facility.'}
    return msg


@router.get('/saf/{proposal_id}')
async def get_proposal(proposal_id: ProposalIn = Depends()):
    saf = await pass_service.get_saf_from_proposal(proposal_id.proposal_id)
    return saf

# URL = "http://n2snadmin.nsls2.bnl.gov:5000"
# client_to_windows = httpx.AsyncClient(base_url=URL, headers={"X-API-KEY": n2sn_service.api_key})


# X-API-KEY
# "gu-999999-9"

# @router.post("/proposal/{proposal_id}")
# async def post_proposal(proposal_id: ProposalIn):
#     await client_to_windows.post("admin/group", json={"name": proposal_id.proposal_id})

# client = motor.motor_asyncio.AsyncIOMotorClient(settings.NSLS2CORE_MONGODB_URI) # @router.get('/proposals') # def get_all_proposals(): #     pass # First get the proposal details from the database # if any of the above are null or zero length, then we don't have # enough information to create any directories # First get the proposal details from the database # @router.post('/proposal/{proposal_id}') # URL = "http://n2snadmin.nsls2.bnl.gov:5000" # client_to_windows = httpx.AsyncClient(base_url=URL, headers={"X-API-KEY": n2sn_service.api_key}) # X-API-KEY # "gu-999999-9" # @router.post("/proposal/{proposal_id}") # async def post_proposal(proposal_id: ProposalIn): #     await client_to_windows.post("admin/group", json={"name": proposal_id.proposal_id})