/*
DESCRIPTION:
Any functions that are used internally by the use-case
*/
package soapi

import (
	"errors"
	"fmt"
	erc "simrs-vx/internal/domain/references/common"
	pl "simrs-vx/pkg/logger"
	pu "simrs-vx/pkg/use-case-helper"
	"time"

	dg "github.com/karincake/apem/db-gorm-pg"
	"gorm.io/gorm"

	ere "simrs-vx/internal/domain/references/encounter"

	ecpl "simrs-vx/internal/domain/main-entities/chemo-plan"
	ecp "simrs-vx/internal/domain/main-entities/chemo-protocol"
	ee "simrs-vx/internal/domain/main-entities/encounter"
	e "simrs-vx/internal/domain/main-entities/soapi"
)

func setData[T *e.CreateDto | *e.UpdateDto](input T, data *e.Soapi) {
	var inputSrc *e.CreateDto
	if inputT, ok := any(input).(*e.CreateDto); ok {
		inputSrc = inputT
	} else {
		inputTemp := any(input).(*e.UpdateDto)
		inputSrc = &inputTemp.CreateDto
	}

	data.Encounter_Id = inputSrc.Encounter_Id
	data.Employee_Id = inputSrc.AuthInfo.Employee_Id
	data.Time = inputSrc.Time
	data.TypeCode = inputSrc.TypeCode
	data.Value = inputSrc.Value
}

func setBulkData(input []e.CreateDto, encounterId uint) []e.Soapi {
	var data []e.Soapi

	for _, v := range input {
		data = append(data, e.Soapi{
			Encounter_Id: &encounterId,
			Employee_Id:  v.Employee_Id,
			Time:         v.Time,
			TypeCode:     v.TypeCode,
			Value:        v.Value,
		})
	}

	return data
}

func validateIfEncounterIsChemo(encounterId uint, event *pl.Event) (*ecpl.ChemoPlan, error) {
	// get encounter
	enc, err := getEncounter(encounterId, event)
	if err != nil {
		return nil, err
	}

	// Encounter must be Ambulatory and NOT Rehab
	a := enc.Ambulatory
	if enc.Class_Code != ere.ECAmbulatory || a == nil || a.Class_Code == ere.ACCRehab {
		return nil, nil
	}

	// get chemo protocol
	chemo, err := getChemo(*enc.Patient_Id, event)
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, nil
		}
		return nil, pl.SetLogError(event, nil)
	}

	if err = chemoProtocoValidation(chemo, event); err != nil {

	}

	if chemo.ChemoPlans == nil || len(*chemo.ChemoPlans) == 0 {
		return nil, nil
	}

	// Return the first chemo plan
	return &(*chemo.ChemoPlans)[0], nil
}

func chemoProtocoValidation(chemo *ecp.ChemoProtocol, event *pl.Event) error {
	if chemo.Status_Code != erc.DVCVerified {
		event.Status = "failed"
		event.ErrInfo = pl.ErrorInfo{
			Code:   "data-not-match",
			Detail: fmt.Sprintf("protocol chemo not yet verified"),
		}

		return pl.SetLogError(event, chemo)
	}

	now := time.Now()
	if now.Before(*chemo.StartDate) || now.After(*chemo.EndDate) {
		event.Status = "failed"
		event.ErrInfo = pl.ErrorInfo{
			Code:   "invalid-date-range",
			Detail: "chemo cannot be performed because the current date is outside the allowed treatment window.",
		}

		return pl.SetLogError(event, chemo)
	}

	return nil
}

func getEncounter(encounterId uint, event *pl.Event) (*ee.Encounter, error) {
	pl.SetLogInfo(event, nil, "started", "getEncounter")
	data := ee.Encounter{}

	var tx = dg.I

	if err := tx.Model(&ee.Encounter{}).
		Where(`"Encounter_Id" = ?`, encounterId).
		Scopes(withConditionalAmbulatory()).
		First(&data).Error; err != nil {
		if processedErr := pu.HandleReadError(err, event, source, nil, encounterId); processedErr != nil {
			return nil, processedErr
		}
	}

	pl.SetLogInfo(event, nil, "complete")
	return &data, nil
}

func withConditionalAmbulatory() func(db *gorm.DB) *gorm.DB {
	return func(db *gorm.DB) *gorm.DB {
		return db.Preload(`"Ambulatory"`, func(db *gorm.DB) *gorm.DB {
			return db.Joins(`JOIN "Encounter" e ON "e"."Id" = "Ambulatory"."Encounter_Id"`)
		})
	}
}

func getChemo(patientId uint, event *pl.Event) (*ecp.ChemoProtocol, error) {
	pl.SetLogInfo(event, nil, "started", "getChemoFromEncounter")
	data := ecp.ChemoProtocol{}

	var tx = dg.I

	tx = tx.Model(&ecp.ChemoProtocol{}).
		Joins(`"ChemoPlan" cp ON cp."Protocol_Id" = "ChemoProtocol"."Id"`).
		Where(`"Patient.Id" = ? AND cp."Status" = ?`, patientId, ere.SPCPlanned).
		Preload("ChemoPlans", func(db *gorm.DB) *gorm.DB {
			return tx.Order(`"Id" ASC`).Limit(1)
		}).
		First(&data)

	if err := tx.Error; err != nil {
		return nil, err
	}

	pl.SetLogInfo(event, nil, "complete")
	return &data, nil
}
