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

import (
	"encoding/json"
	erc "simrs-vx/internal/domain/references/common"
	ere "simrs-vx/internal/domain/references/encounter"
	erp "simrs-vx/internal/domain/references/person"
	pl "simrs-vx/pkg/logger"
	"strconv"

	e "simrs-vx/internal/domain/main-entities/encounter"
	epa "simrs-vx/internal/domain/main-entities/person-address"
	epc "simrs-vx/internal/domain/main-entities/person-contact"
	epr "simrs-vx/internal/domain/main-entities/person-relative"
	ep "simrs-vx/internal/domain/simgos-entities/m-pasien"
	etph "simrs-vx/internal/domain/simgos-entities/t-pemeriksaan-hist"
	etp "simrs-vx/internal/domain/simgos-entities/t-pendaftaran"
	esync "simrs-vx/internal/domain/sync-entities/encounter"
	esyncLog "simrs-vx/internal/domain/sync-entities/log"

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

func setDataCreateTPendaftaran(input *e.Encounter) (data etp.TPendaftaran, err error) {
	data.Nomr = *input.Patient.Number

	// set pasienbaru
	if input.NewStatus {
		data.Pasienbaru = 1
	}

	// default kelas daftar
	data.KelasDaftar = 3

	// set noAntrian
	defaultFromSimx := "rehab"
	data.NoAntrian = &defaultFromSimx

	// set no_kunjung
	noKunjung, err := generateNoKunjung()
	if err != nil {
		return etp.TPendaftaran{}, err
	}
	data.NoKunjung = uint(noKunjung)

	if p := input.Patient.Person; p != nil {
		mapRelative(p.Relatives, &data)

		if data.PenanggungjawabNama == "" {
			data.PenanggungjawabHubungan = "DIRI SENDIRI"
			data.PenanggungjawabNama = p.Name
			mapContact(p.Contacts, &data)
			mapAddress(p.Addresses, &data)
		}

		if bpjs := p.VclaimMember; bpjs != nil {
			data.Nokartu = *bpjs.CardNumber
		}
	}

	setDataTPendaftaran(input, &data)

	return
}

func setDataTPendaftaran(input *e.Encounter, data *etp.TPendaftaran) {
	data.Tglreg = input.RegisteredAt

	// set kdpoly
	if p := input.Unit_Code; p != nil {
		kdpoly, _ := strconv.Atoi(*input.Unit_Code)
		data.Kdpoly = uint(kdpoly)
	}

	// set kdrujuk
	setKdrujuk(input.RefType_Code, data)

	// set kdcarabayar
	setKdcarabayar(input.PaymentMethod_Code, data)

	// TODO: shift

	// set nip
	if emp := input.Adm_Employee; emp != nil {
		if user := emp.User; user != nil {
			data.Nip = user.Name
		}
	}

	// set data if payment_method bpjs
	if input.RefSource_Name != nil {
		data.Ketrujuk = *input.RefSource_Name
		data.Strujukan = 1
	} else {
		data.Ketrujuk = "TR"
		data.Strujukan = 0
	}

	if ref := input.VclaimReference; ref != nil {
		data.Norujukan = *ref.Number
		data.Tglrujukan = ref.Date
	}

	data.Jamreg = input.RegisteredAt

	// TODO: st_asal_masuk

	return
}

func setDataUpdateStatus(input e.Encounter, data *etp.TPendaftaran) {
	switch input.Status_Code {
	case erc.DSCProcess:
		data.Masukpoly = &now
	}

	if input.Status_Code == erc.DSCCancel {
		data.Status = 11
		return
	}

	if input.Discharge_Method_Code != nil {
		setStatus(input.Discharge_Method_Code, data)
	}
}

func setDataCheckIn(input e.Encounter, data *etp.TPendaftaran) {
	// set kddokter
	kddokter, _ := strconv.Atoi(*input.Responsible_Doctor_Code)
	data.Kddokter = uint(kddokter)

	// set petugas_klinik
	// only update once
	if data.PetugasKlinik == "" {
		if n := input.Responsible_Nurse; n != nil {
			if emp := n.Employee; emp != nil {
				if u := emp.User; u != nil {
					data.PetugasKlinik = u.Name
				}
			}
		}
	}

	data.Masukpoly = input.StartedAt
	return
}

func setDataCheckOut(input e.Encounter, data *etp.TPendaftaran) {
	data.Keluarpoly = input.FinishedAt

	// set status
	setStatus(input.Discharge_Method_Code, data)
	if data.Status == 8 || data.Status == 3 {
		if dc := input.DeathCause; dc != nil {
			var dcSrc []DeathCauseSrc
			_ = json.Unmarshal([]byte(*dc.Value), &dcSrc)

			for i, v := range dcSrc {
				switch i {
				case 0:
					data.SebabMati1 = &v.Name
					data.IcdMati1 = &v.Code
				case 1:
					data.SebabMati2 = &v.Name
					data.IcdMati2 = &v.Code
				case 2:
					data.SebabMati3 = &v.Name
					data.IcdMati3 = &v.Code
				default:
					break
				}
			}
		}
		death := uint(1)
		data.StMeninggal = &death
		data.DtMeninggal = data.Keluarpoly
	}

	setPelayanan := uint(1)
	data.StPelayanan = &setPelayanan

	return
}

func setDataRequestSwitchUnit(input e.Encounter, data *etp.TPendaftaran) (hist []etph.TPemeriksaanHist) {
	if enc := input.InternalReferences; enc != nil {
		for i, ref := range *enc {
			hist = append(hist, etph.TPemeriksaanHist{
				Idxdaftar:       &data.Idxdaftar,
				Kdpoly:          stringtouint(*ref.Unit_Code),
				DokterPengonsul: stringtouint(*ref.SrcDoctor_Code),
				DokterPenerima:  stringtouint(*ref.Doctor_Code),
				StartKonsul:     &now,
			})

			// set user konsul
			if n := input.Responsible_Nurse; n != nil {
				if emp := n.Employee; emp != nil {
					if u := emp.User; u != nil {
						hist[i].UserKonsul = &u.Name
					}
				}
			}

			// setaktif
			aktif := uint16(1)
			hist[i].StAktif = &aktif
		}
	}
	return
}

func setKdrujuk(input *ere.RefTypeCode, data *etp.TPendaftaran) {
	switch *input {
	case ere.RTCGov:
		data.Kdrujuk = 1
	case ere.RTCPrivate:
		data.Kdrujuk = 2
	case ere.RTCBpjs:
		data.Kdrujuk = 3
	default:
		data.Kdrujuk = 0
	}
}

func setKdcarabayar(input ere.AllPaymentMethodCode, data *etp.TPendaftaran) {
	switch input {
	case ere.APMCPks:
		data.Kdcarabayar = 12
	case ere.APMCJkn:
		data.Kdcarabayar = 5
	case ere.APMCJkmm:
		data.Kdcarabayar = 9
	case ere.APMCSpm:
		data.Kdcarabayar = 10
	default:
		data.Kdcarabayar = 1
	}
}

func setStatus(code *ere.DischargeMethodCode, data *etp.TPendaftaran) {
	switch *code {
	case ere.DMCHome:
		data.Status = 1
	case ere.DMCInpatient:
		data.Status = 2
	case ere.DMCDeathOnArrival:
		data.Status = 3
		data.StMeninggal = func(u uint) *uint { return &u }(1)
		data.DtMeninggal = &now
	case ere.DMCDeath:
		data.Status = 8
		data.StMeninggal = func(u uint) *uint { return &u }(1)
		data.DtMeninggal = &now
	case ere.DMCHomeReq:
		data.Status = 7
	case ere.DMCConsulPoly:
		data.Status = 5
	case ere.DMCExtRef:
		data.Status = 6
	case ere.DMCConsulChDay:
		data.Status = 12
	case ere.DMCEmergency:
		data.Status = 10
	case ere.DMCEmergencyCovid:
		data.Status = 13
	case ere.DMCConsulExecutive:
		data.Status = 15
	case ere.DMCConsulBack:
		data.Status = 100
	default:
		data.Status = 0
	}
}

func mapRelative(relative *[]epr.PersonRelative, data *etp.TPendaftaran) {
	if relative == nil || len(*relative) == 0 {
		return
	}

	for _, r := range *relative {
		if r.Responsible {
			data.PenanggungjawabNama = *r.Name

			switch r.Relationship_Code {
			case erp.RCMother, erp.RCFather:
				data.PenanggungjawabHubungan = "ORANG TUA"
			case erp.RCChild:
				data.PenanggungjawabHubungan = "ANAK"
			case erp.RCSibling:
				data.PenanggungjawabHubungan = "SAUDARA"
			case erp.RCUncle, erp.RCAunt,
				erp.RCGdMother, erp.RCGdFather,
				erp.RCNephew, erp.RCGdChild:
				data.PenanggungjawabHubungan = "KELUARGA LAIN"
			case erp.RCFriend:
				data.PenanggungjawabHubungan = "TEMAN"
			case erp.RCSpouse:
				data.PenanggungjawabHubungan = "SUAMI/ISTRI"
			case erp.RCSelf:
				data.PenanggungjawabHubungan = "DIRI SENDIRI"
			default:
				data.PenanggungjawabHubungan = "LAINNYA ..."
			}

			data.PenanggungjawabAlamat = *r.Address
			data.PenanggungjawabPhone = *r.PhoneNumber

			break
		}
	}
}

func setDataSimxLog(input *esyncLog.SimxLogDto) (data esync.EncounterSimxLog) {
	// encode to JSON
	jsonData, _ := json.MarshalIndent(input.Payload, "", "  ")
	jsonString := string(jsonData)

	var status erc.ProcessStatusCode
	if input.IsSuccess {
		status = erc.PSCSuccess
	} else {
		status = erc.PSCFailed
		if input.ErrMessage != nil {
			data.ErrMessage = input.ErrMessage
		}
	}

	data.Value = &jsonString
	data.Date = &now
	data.Status = status

	return
}

func setDataSimxLink(simxId, simgosId uint) (data esync.EncounterLink) {
	data.Simx_Id = simxId
	data.Simgos_Id = simgosId
	return
}

func generateNoKunjung() (int, error) {
	var lastNumber int

	// Lock rows for this prefix → prevents race condition
	if err := dg.IS["simrs"].
		Table("t_pendaftaran").
		Select("no_kunjung").
		Where("tglreg = CURRENT_DATE").
		Order("no_kunjung DESC").
		Clauses(clause.Locking{Strength: "UPDATE"}).
		Limit(1).
		Scan(&lastNumber).Error; err != nil {
		return 0, err
	}

	return lastNumber + 1, nil
}

func stringtouint(v string) *uint {
	u, _ := strconv.Atoi(v)
	point := uint(u)
	return &point
}

func updatePatientCaraBayar(input etp.TPendaftaran, event *pl.Event, dbx ...*gorm.DB) error {
	pl.SetLogInfo(event, nil, "started", "DBUpdate")

	var tx *gorm.DB
	if len(dbx) > 0 {
		tx = dbx[0]
	} else {
		tx = dg.IS["simrs"]
	}

	if err := tx.Model(&ep.MPasien{}).
		Where("\"nomr\" = ?", input.Nomr).
		Updates(map[string]interface{}{
			"kdcarabayar": input.Kdcarabayar,
		}).Error; err != nil {
		event.Status = "failed"
		event.ErrInfo = pl.ErrorInfo{
			Code:   "update-fail",
			Detail: "Failed to update patient",
			Raw:    err,
		}
		return pl.SetLogError(event, input)
	}

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

func mapContact(contact *[]epc.PersonContact, data *etp.TPendaftaran) {
	if contact == nil || len(*contact) == 0 {
		return
	}

	for _, c := range *contact {
		if c.Type_Code == erp.CTPhone || c.Type_Code == erp.CTMPhone {
			data.PenanggungjawabPhone = c.Value
			break
		}
	}
}

func mapAddress(addresses *[]epa.PersonAddress, data *etp.TPendaftaran) {
	if addresses == nil || len(*addresses) == 0 {
		return
	}
	a := (*addresses)[0]
	data.PenanggungjawabAlamat = a.Address
}
