import base64
from datetime import UTC, datetime
from json import dumps
from logging import getLogger
from secrets import token_urlsafe
from typing import Annotated

from fastapi import APIRouter, Body, Depends, Response, UploadFile, status
from orjson import loads

from apps.remd.dependencies import convert_aemd_to_pdf, get_parsable_ids
from apps.tdn.auth import token
from apps.users.auth import login
from apps.users.models import User
from clients import clients as c
from clients.tmk import schema as ts
from clients.vitacore import schema as vs
from shared import exceptions as e
from shared.redis import client as cache

from . import schema as s

logger = getLogger(__name__)
router = APIRouter(
    prefix='/user',
    tags=[
        'User',
    ],
)


@router.get(
    '/getProfile',
    responses={
        status.HTTP_200_OK: {'model': vs.ProfileModel},
    },
)
async def get_profile(user: Annotated[User, Depends(login)]):
    """
    Get profile of user.
    """
    return await c.vitacore_api.getProfile(user.vita_id)


@router.get(
    '/getDepartments',
    responses={
        status.HTTP_200_OK: {'model': vs.OrganizationsModel},
    },
)
async def get_departments():
    """
    Get list of departments.
    """
    return await c.vitacore_api.getDepartments()


@router.get(
    '/getWorkers', responses={status.HTTP_200_OK: {'model': vs.WorkersModel}}
)
async def get_workers(
    user: Annotated[User, Depends(login)], departmentId: str
):
    """
    Get list of workers by department id.
    """
    return await c.vitacore_api.getWorkers(departmentId)


@router.get(
    '/getSpecs',
    responses={status.HTTP_200_OK: {'model': vs.SpecsV021Model}},
)
async def get_specs(user: Annotated[User, Depends(login)]):
    """
    Get list of specialties.
    """
    return await c.vitacore_api.getSpecsV021()


@router.get(
    '/getEntries', responses={status.HTTP_200_OK: {'model': vs.EntriesModel}}
)
async def get_entries(user: Annotated[User, Depends(login)]):
    """
    Get list of entries for user by id.
    """
    return await c.vitacore_api.getEntries(user.vita_id)


@router.get('/getVaccsReport')
async def get_vaccs_report(
    user: Annotated[User, Depends(login)], resultId: str | None = None
):
    """
    Get report of vaccinations for user by id.
    """
    if resultId is not None:
        return await c.vitacore_api.getDiagResultFile(resultId)

    return await c.vitacore_api.getVaccsReport(user.vita_id)


@router.get('/getVaccsReport/download')
async def get_vaccs_report_download(
    user: Annotated[User, Depends(login)], resultId: str | None = None
):
    """
    Get report of vaccinations for user by id.
    """
    if resultId is not None:
        file = await c.vitacore_api.getDiagResultFile(resultId)
    else:
        file = await c.vitacore_api.getVaccsReport(user.vita_id)

    if file is None:
        raise e.BadRequestException

    file_bytes = base64.b64decode(file.content)
    filename = f'vaccs_report_{resultId or user.vita_id}.doc'

    return Response(
        content=file_bytes,
        media_type='application/msword',
        headers={'Content-Disposition': f'attachment; filename="{filename}"'},
    )


@router.get('/getMedExamDict')
async def get_med_exam_dict(user: Annotated[User, Depends(login)]):
    """
    Get medical examination dictionary.
    """
    return await c.vitacore_api.getMedExamDict()


@router.get('/getRoutesList')
async def get_routes_list(user: Annotated[User, Depends(login)]):
    """
    Get list of routes.
    """
    return await c.vitacore_api.getRoutesList(user.vita_id)


@router.get('/getHospExaminations')
async def get_hosp_examinations(
    user: Annotated[User, Depends(login)], examId: str | None = None
):
    """
    Get list of hospital examinations.
    """
    return await c.vitacore_api.getHospExaminations(
        user.vita_id,
        examId,
    )


@router.get('/getCurrHosp')
async def get_curr_hosp(user: Annotated[User, Depends(login)]):
    """
    Get current hospitalization.
    """
    return await c.vitacore_api.getCurrHosp(user.vita_id)


@router.get('/getHosps')
async def get_hosps(user: Annotated[User, Depends(login)]):
    """
    Get list of hospitals.
    """
    return await c.vitacore_api.getHosps(user.vita_id)


@router.get('/getHospRecommendations')
async def get_hosp_recommendations(user: Annotated[User, Depends(login)]):
    """
    Get list of recommended hospitals.
    """
    return await c.vitacore_api.getHospRecommendations(user.vita_id)


@router.get('/getHospRoutes')
async def get_hosp_routes(user: Annotated[User, Depends(login)]):
    """
    Get list of recommended hospitals.
    """
    return await c.vitacore_api.getHospRoutes(user.vita_id)


@router.get('/getDiagnosticResults')
async def get_diagnostic_results(user: Annotated[User, Depends(login)]):
    """
    Get list of diagnostic results.
    """
    return await c.vitacore_api.getDiagnosticResults(user.vita_id)


@router.get('/getELNs')
async def get_eln(user: Annotated[User, Depends(login)]):
    """
    Get list of ELNs.
    """
    return await c.vitacore_api.getELNs(user.vita_id)


@router.get('/getPatFLG')
async def get_pat_flg(user: Annotated[User, Depends(login)]):
    """
    Get list of ELNs.
    """
    return await c.vitacore_api.getPatFLG(user.vita_id)


@router.get('/queue', response_model=list[ts.QueueModel])
async def queue(user: Annotated[User, Depends(login)]):
    """
    Get list of VKS queues.
    """
    profile = await c.vitacore_api.getProfile(user.vita_id)
    return await c.tmk_api.getQueue(patient_snils=profile.SNILS)


@router.get('/aemd')
async def get_aemd(
    user: Annotated[User, Depends(login)],
    parsable_ids: Annotated[list[str], Depends(get_parsable_ids)],
):
    profile = await c.vitacore_api.getProfile(user.vita_id)
    snils = profile.SNILS.replace('-', '').replace(' ', '')
    docs = await c.aemd_api.searchRegistryItem(patient_snils=snils)
    items: list[s.AEMDFile] = docs['items']
    return_items: list[s.AEMDReturnFile] = []

    for item in items:
        DocKind = item['emdrId'].split('.')[0]
        if DocKind not in parsable_ids:
            continue

        is_cached = await cache.get(f'aemd:{user.vita_id}:{item["emdrId"]}')

        return_items.append(
            s.AEMDReturnFile(
                emdrId=item['emdrId'],
                registrationDate=item['registrationDate'],
                DocKind=DocKind,
                IsSemd=item['IsSemd'],
                isCached=bool(is_cached),
            )
        )

    return return_items


@router.post('/aemd', status_code=status.HTTP_202_ACCEPTED)
async def post_aemd(user: Annotated[User, Depends(login)], emdrId: str):
    messageId = token_urlsafe(32)
    data = s.AEMDDemandContent(
        messageId=messageId, emdrId=emdrId, vitaId=user.vita_id
    )
    await cache.set(f'aemd_messages:{messageId}', dumps(data))
    await c.aemd_api.demandContent(messageId=messageId, emdrId=emdrId)


@router.get('/aemd/{emdrId}')
async def get_aemd_file(
    user: Annotated[User, Depends(login)], emdrId: str, docKind: str
):
    data = await cache.get(f'aemd:{user.vita_id}:{emdrId}')

    if not data:
        raise e.NotFoundException(status_code=404, detail='File not found')

    b64 = loads(data)['data']
    decoded = base64.b64decode(b64)
    pdf = await convert_aemd_to_pdf(decoded, docKind)
    b64_pdf = base64.b64encode(pdf).decode('utf-8')

    return {
        'filename': f'{emdrId}.pdf',
        'content_type': 'application/pdf',
        'data': b64_pdf,
    }


@router.get('/aemd/{emdrId}/download')
async def get_aemd_file_download(
    user: Annotated[User, Depends(login)], emdrId: str, docKind: str
):
    data = await cache.get(f'aemd:{user.vita_id}:{emdrId}')

    if not data:
        raise e.NotFoundException(status_code=404, detail='File not found')

    b64 = loads(data)['data']
    decoded = base64.b64decode(b64)
    pdf_bytes = await convert_aemd_to_pdf(decoded, docKind)
    filename = f'{emdrId}.pdf'

    return Response(
        content=pdf_bytes,
        media_type='application/pdf',
        headers={'Content-Disposition': f'attachment; filename="{filename}"'},
    )


@router.post('/measurement', status_code=status.HTTP_202_ACCEPTED)
async def measurement(
    tdn_access_token: Annotated[str, Depends(token)],
    user: Annotated[User, Depends(login)],
    ad: Annotated[int, Body()],
    sd: Annotated[int, Body()],
    pulse: Annotated[int, Body()],
    comment: Annotated[str, Body()],
    status: Annotated[int, Body(ge=1, le=3)],
    serial_number: Annotated[str, Body()],
    ekg: UploadFile,
):
    patient = await c.tdn_api.patient_search(tdn_access_token, user.vita_id)
    patientUid = patient['items'][0]['uid']
    observations = await c.tdn_api.observations_search(
        tdn_access_token, patientUid
    )

    if observations.total == 0:
        raise e.NotFoundException(detail='No observations found')

    ad_obsrvMeasurementUid = None
    ad_observationUid = None
    health_obsrvMeasurementUid = None
    health_observationUid = None

    sad_measurement = None
    dad_measurement = None
    pulse_measurement = None
    health_measurement = None
    health_measurement_created_at = None

    observations = observations.items[::-1]
    for observation in observations:
        observation_measurements = (
            await c.tdn_api.observations_measurement_search(
                tdn_access_token, observation.uid
            )
        )

        for measurement in observation_measurements.items:
            if measurement.measurement.code == 'ADPULSE':
                ad_obsrvMeasurementUid = measurement.uid
                ad_observationUid = measurement.observationUid

                for metric in measurement.obsrvMtMetrics:
                    if metric.metric.code == 'SAD':
                        sad_measurement = metric.uid

                    if metric.metric.code == 'DAD':
                        dad_measurement = metric.uid

                    if metric.metric.code == 'PULSE':
                        pulse_measurement = metric.uid

            if measurement.measurement.code == 'HEALTH':
                health_obsrvMeasurementUid = measurement.uid
                health_observationUid = measurement.observationUid

                for metric in measurement.obsrvMtMetrics:
                    if metric.metric.code == 'HEALTH' and (
                        health_measurement_created_at is None
                        or metric.createdAt > health_measurement_created_at
                    ):
                        health_measurement = metric.uid
                        health_measurement_created_at = metric.createdAt

        if (
            not ad_obsrvMeasurementUid
            or not sad_measurement
            or not dad_measurement
            or not pulse_measurement
            or not ad_observationUid
            or not health_obsrvMeasurementUid
            or not health_observationUid
            or not health_measurement
            or not health_measurement_created_at
        ):
            ad_obsrvMeasurementUid = None
            sad_measurement = None
            dad_measurement = None
            pulse_measurement = None
            ad_observationUid = None
            health_obsrvMeasurementUid = None
            health_observationUid = None
            health_measurement = None
            health_measurement_created_at = None

        else:
            break

    if not ad_obsrvMeasurementUid or not ad_observationUid:
        raise e.NotFoundException(detail='No ADPULSE measurement found')

    if not sad_measurement:
        raise e.NotFoundException(detail='No SAD measurement found')

    if not dad_measurement:
        raise e.NotFoundException(detail='No DAD measurement found')

    if not pulse_measurement:
        raise e.NotFoundException(detail='No PULSE measurement found')

    if not health_obsrvMeasurementUid or not health_observationUid:
        raise e.NotFoundException(detail='No HEALTH measurement found')

    if not health_measurement:
        raise e.NotFoundException(detail='No HEALTH measurement found')

    if not health_obsrvMeasurementUid or not health_observationUid:
        raise e.NotFoundException(detail='No HEALTH measurement found')

    ad_series = await c.tdn_api.create_series(
        tdn_access_token,
        ad_observationUid,
        ad_obsrvMeasurementUid,
    )

    ad_series_uid = ad_series.uid

    # SAD
    await c.tdn_api.create_series_values(
        tdn_access_token, ad_series_uid, sad_measurement, nvalue=sd
    )

    # DAD
    await c.tdn_api.create_series_values(
        tdn_access_token, ad_series_uid, dad_measurement, nvalue=ad
    )

    # PULSE
    await c.tdn_api.create_series_values(
        tdn_access_token, ad_series_uid, pulse_measurement, nvalue=pulse
    )

    health_series = await c.tdn_api.create_series(
        tdn_access_token,
        health_observationUid,
        health_obsrvMeasurementUid,
    )

    health_series_uid = health_series.uid

    # HEALTH
    await c.tdn_api.create_series_values(
        tdn_access_token,
        health_series_uid,
        health_measurement,
        nvalue=status,
        svalue=comment,
    )

    # EKG
    await c.tdn_api.ekg(
        tdn_access_token,
        patientUid,
        serial_number,
        ekg,
    )

    created = datetime.now(UTC).strftime('%Y-%m-%d %H:%M:%S')
    data = {
        'ad': ad,
        'sd': sd,
        'pulse': pulse,
        'created_at': created,
        'comment': comment,
        'status': status,
    }
    cache_key = f'tdn:measurement:{user.id}:{created}'
    await cache.set(cache_key, dumps(data))


@router.get('/measurements')
async def measurements(
    user: Annotated[User, Depends(login)],
):
    data = [
        await cache.get(key)
        for key in await cache.keys(f'tdn:measurement:{user.id}:*')
    ]
    return data


@router.delete('/account', status_code=status.HTTP_204_NO_CONTENT)
async def delete_account(user: Annotated[User, Depends(login)]):
    return


@router.get('/notifications')
async def notifications(user: Annotated[User, Depends(login)]):
    keys = await cache.keys(f'tmk:{user.id}:*')
    notif: list[dict[str, str | bool]] = []

    for key in keys:
        val = await cache.get(key)

        if val is None:
            continue

        value = loads(val)
        notif_val = value.copy()
        notif.append(notif_val)

        if value['is_read'] is False:
            value['is_read'] = True
            await cache.set(key, dumps(value))

    return s.Notifications(
        notifications=notif,
    )


@router.post('/complaint', status_code=status.HTTP_204_NO_CONTENT)
async def complaint(
    user: Annotated[User, Depends(login)], complaints: s.Complaints
):
    cache_key = f'complaint:{user.vita_id}'
    await cache.set(cache_key, dumps(complaints.complaints))
