using adas_core.Application.Exceptions;
using adas_core.Application.Services.Interfaces;
using adas_core.Domain.Enums;
using adas_core.Domain.Models;
using adas_core.Domain.Models.AppSettings;
using adas_core.Domain.Models.DTO;
using adas_core.Domain.Models.MongoModels;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using MongoDB.Bson;
using Serilog;
using Patient = adas_core.Domain.Models.MongoModels.Patient;

namespace adas_core.Application.Services;

public class AdminPanelService(
    IOptions<ApiSettings> apiSettings,
    IPatientService patientService,
    IConfigObservationService configObservationService,
    IMedicineService medicineService,
    IPointOfCareService pocService,
    IUnitService unitService,
    ILogger<AdminPanelService> logger,
    IAdmissionService admissionService,
    IAuthService authService,
    IDischargeService dischargeService,
    IDisplayService displayService)
    : IAdminPanelService
{
    private readonly List<string> _defaultIdRecord = apiSettings.Value.DefaultIdRecord ?? [];


    #region Patient

    public async Task<bool> ArchivePatient(Patient patient)
    {
        try
        {
            await patientService.ArchivePatient(patient);

            logger.LogDebug("archived patientid {patientid} from ADMPanel", patient.Id);
            return true;
        }
        catch (Exception e)
        {
            Console.WriteLine(e);
            throw;
        }
    }

    public async Task<Patient?> CreatePatient(AdmPanelRequest admRequest)
    {
        var patient = new Patient
        {
            Id = ObjectId.GenerateNewId()
        };
        await UpdateNewPatient(patient, admRequest);
        await patientService.Insert(patient);

        logger.LogDebug("Inserted {patientid} from ADMPanel", patient.Id);
        return patient;
    }

    private async Task UpdateNewPatient(Patient patient, AdmPanelRequest admRequest)
    {
        if (!string.IsNullOrEmpty(admRequest.PatientNumber)) patient.PatientNumber = admRequest.PatientNumber;
        if (admRequest.AdmTime.HasValue) patient.AdmTime = admRequest.AdmTime;
        if (admRequest.Patient != null && !admRequest.Patient.IsEmptyDontCheckIds())
            patient.Person = admRequest.Patient;
        if (admRequest.Patient != null && (admRequest.Patient.Ids == null || admRequest.Patient.Ids.Count == 0))
        {
            if (patient.Person is { Ids: null }) patient.Person.Ids = new Dictionary<string, string>();
            foreach (var item in _defaultIdRecord) patient.Person?.Ids?.Add(item, admRequest?.PatientNumber ?? "null");
        }

        patient.Person?.SetIds(patient.Person?.Ids ?? new Dictionary<string, string>());

        if (admRequest is { UnitId: not null })
        {
            var unit = await unitService.FindById(admRequest.UnitId);
            patient.UnitId = unit?.Id ?? admRequest.UnitId;
            patient.UnitString = unit?.Name;


            if (admRequest is { PointOfCareId: not null })
            {
                //El paciente existe en la localizacion no te dejo insertarlo
                var patientInLocation = await patientService.FindByPointOfCareId(admRequest.PointOfCareId.Value);
                if (patientInLocation != null)
                {
                    logger.LogError(
                        "trying to insert patientid {patientid} in to location already in use. PointOfcareId: {poc}",
                        patient.Id, patient.PointOfCareId);
                    throw new Exception($"patient already in location: {patient.Location}");
                }

                var poc = await pocService.FindById(admRequest.PointOfCareId.Value);
                if (poc == null)
                {
                    logger.LogError(
                        "trying to insert patientid {patientid} in to location not found. PointOfcareId: {poc}",
                        patient.Id, admRequest.PointOfCareId);
                    throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
                }

                await pocService.SetPointOfCareStatus(poc.Id, StatusEnum.PointOfCare.InUse);

                patient.Location = new PatientLocation
                (
                    bed: poc.Bed,
                    room: poc.Room,
                    unitName: unit?.Name
                );
                patient.PointOfCareId = poc.Id;
            }
            else
            {
                var pocUnknown =
                    await pocService.FindByBedAndUnitId(VirtualPointOfCare.Unknown.ToString(), patient.UnitId);

                patient.PointOfCareId = pocUnknown?.Id;
            }

            patient.UpdateDate = DateTime.UtcNow;
        }
    }

    public async Task<Patient?> FindPatientById(ObjectId id)
    {
        return await patientService.FindById(id) ??
               throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
    }

    public async Task<Patient?> FindPatientByLocation(PatientLocation location)
    {
        return await patientService.FindByLocation(location);
    }

    public async Task<Patient?> FindPatientByPatientNumber(string patientNumber)
    {
        return await patientService.FindByPatientNumber(patientNumber);
    }

    public async Task<bool> UpdatePatientData(AdmPanelRequest request, Patient oldPatient)
    {
        //traer el paciente que se quiere actualizar con los valores que tenga en la base de datos a una variable
        //actualizar unicamente los campos que tengan que ver con los datos del paciente
        if (request.PatientNumber == null || request.Patient == null || request.Patient.IsEmptyDontCheckIds())
        {
            logger.LogError("Patient Data not updated. Old Patient:{oldPatient}. Api Request {request}", oldPatient,
                request);
            throw new ConflictException(HttpEnum.ErrorMessage.ConflictUpdateFailed);
        }

        var patientNumberChanged = oldPatient.PatientNumber != request.PatientNumber;
        await patientService.UpdatePatientData(oldPatient.Id, request.PatientNumber, request.Patient,
            patientNumberChanged);

        return true;
    }

    public async Task<bool> UpdatePatientLocation(AdmPanelRequest request)
    {
        var patient = await patientService.FindByLocation(request.OldLocation);

        var patientExistsInLocation = await patientService.FindByLocation(request.Location);

        //Si ya hay un paciente en esa localización 
        if (patientExistsInLocation != null)
            //Si hay un paciente distinto en la nueva localización movemos al anterior
            if (patient != null && patientExistsInLocation.PatientNumber != patient.PatientNumber)
                await patientService.UpdateLocation(patientExistsInLocation.Id,
                    new PatientLocation(VirtualPointOfCare.Pushed.ToString(), ObjectId.GenerateNewId().ToString()));

        if (patient != null)
        {
            if (request.Location?.Bed == null || string.IsNullOrEmpty(request.Location.Bed))
                request.Location = new PatientLocation(VirtualPointOfCare.Pushed.ToString(),
                    ObjectId.GenerateNewId().ToString());


            //to solve problems when empty beds with not scapped ""
            request.Location.Bed = request.Location?.Bed?.Replace("\"", "");

            await patientService.UpdateLocation(patient.Id, request.Location);
            patient = await patientService.FindById(patient.Id);
            if (patient != null) await patientService.Update(patient);
        }
        else
        {
            patient = new Patient
            {
                Id = ObjectId.GenerateNewId()
            };
            await patientService.Insert(patient);
        }

        return true;
    }

    public async Task<Patient?> FindPatient(AdmPanelRequest request)
    {
        var findByLocation = !request.Location?.IsFullEmpty();

        return await patientService.FindPatient(request.PatientId, request.PatientNumber, request.Location,
            findByLocation ?? false) ?? throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
    }

    public async Task<Patient?> FindByLocation(PatientLocation location)
    {
        return await patientService.FindByLocation(location) ??
               throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
    }

    #endregion

    #region ConfigObservations

    public async Task<bool> CreateConfig(ConfigObservation configObservation)
    {
        _ = await configObservationService.CreateConfig(configObservation) ??
            throw new ConflictException(HttpEnum.ErrorMessage.ConflictCreationFailed);
        return true;
    }

    public async Task<bool> UpdateConfig(ConfigObservation configObservation)
    {
        _ = await configObservationService.UpdateConfig(configObservation) ??
            throw new ConflictException(HttpEnum.ErrorMessage.ConflictUpdateFailed);
        return true;
    }

    public async Task<bool> DeleteConfigObservationItem(ObjectId id)
    {
        _ = await configObservationService.RemoveConfigItem(id) ??
            throw new ConflictException(HttpEnum.ErrorMessage.ConflictDeleteFailed);
        return true;
    }

    #endregion

    #region Unit

    public async Task<Unit?> InsertUnit(Unit unit)
    {
        //Insertamos la unidad y creamos los PoCs por defecto para esa unidad
        var result = await unitService.InsertOne(unit);
        if (result != null)
        {
            // Por cada valor del enum VirtualPointOfCare, creamos un PointOfCare asociado a la unidad
            foreach (var pocEnum in Enum.GetValues(typeof(VirtualPointOfCare)).Cast<VirtualPointOfCare>())
            {
                var poc = new PointOfCare
                {
                    UnitId = result.Id,
                    Status = StatusEnum.PointOfCare.Available,
                    Room = pocEnum.ToString(),
                    Bed = pocEnum.ToString()
                };
                var insertedPoc = await pocService.InsertPointOfCare(poc);
                if (insertedPoc != null)
                {
                    result.PointOfCareIds ??= [];
                    result.PointOfCareIds.Add(insertedPoc.Id);
                }
            }

            // Actualizamos la unidad con los nuevos PointOfCareIds
            await unitService.UpdateUnit(result);
        }

        return result;
    }


    public async Task<UnitInfoDto?> GetUnitDependencyDto(Unit unit)
    {
        try
        {
            return new UnitInfoDto(unit)
            {
                Admissions = await admissionService.CountAdmissionsByUnitId(unit.Id),
                Discharges = await dischargeService.CountDischargesByUnitId(unit.Id),
                Displays = await displayService.CountDisplaysByUnitId(unit.Id),
                Patients = await patientService.CountPatientsByUnitId(unit.Id),
                PointOfCares = await pocService.CountPoCsByUnitId(unit.Id),
                VirtualPointOfCares = await pocService.CountVirtualPoCsByUnitId(unit.Id)
            };
        }
        catch (Exception e)
        {
            logger.LogError(e.Message);
            return null;
        }
    }

    public async Task<bool> DeleteUnitById(ObjectId unitId)
    {
        try
        {
            var unit = await unitService.FindById(unitId);

            if (unit == null) throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);

            //No se puede borrar una unidad que est� en uso 
            var patients = await patientService.CountPatientsByUnitId(unitId);
            if (patients > 0)
                //Si tiene pacientes no se permite borrado
                throw new ConflictException(HttpEnum.ErrorMessage.ConflictResourceInUse);

            //Borramos admisiones
            await admissionService.DeleteAdmissionsByUnitId(unitId);
            //discharges
            await dischargeService.DeleteDischargesByUnitId(unitId);
            //Borramos Authorizations
            await authService.DeleteByUnitId(unitId);

            //Displays 
            await displayService.DeleteDisplaysByUnitId(unitId);

            //PointOfCares
            await pocService.DeletePoCsByUnitId(unitId);


            //Una vez borrados los recursos asociados a la unidad, borramos la unidad
            await unitService.DeleteUnitById(unit);

            return true;
        }
        catch (Exception e)
        {
            Log.Error(e.Message);
            return false;
        }
    }

    #endregion

    #region Medicine

    public async Task<Medicine?> GetMedicineById(ObjectId medicineId)
    {
        return await medicineService.GetMedicineById(medicineId) ??
               throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
    }

    public async Task<Medicine?> PostMedicine(Medicine medicine)
    {
        var newMedicine = await medicineService.PostMedicine(medicine) ??
                          throw new ConflictException(HttpEnum.ErrorMessage.ConflictCreationFailed);
        return newMedicine;
    }

    public async Task<Medicine?> UpdateMedicine(Medicine medicine)
    {
        var updatedMedicine = await medicineService.UpdateMedicine(medicine) ??
                              throw new ConflictException(HttpEnum.ErrorMessage.ConflictUpdateFailed);
        return updatedMedicine;
    }

    public async Task<bool> DeleteMedicineById(string medicineId)
    {
        if (!ObjectId.TryParse(medicineId, out var objectId))
            throw new BadRequestException(HttpEnum.ErrorMessage.BadRequestInvalidFormat);
        await medicineService.DeleteMedicineById(objectId);
        _ = await medicineService.GetMedicineById(ObjectId.Parse(medicineId)) ??
            throw new ConflictException(HttpEnum.ErrorMessage.ConflictDeleteFailed);
        return true;
    }

    #endregion
}