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

import (
	"encoding/json"
	"fmt"
	erc "simrs-vx/internal/domain/references/common"
	erp "simrs-vx/internal/domain/references/person"
	"strconv"

	e "simrs-vx/internal/domain/main-entities/patient"
	ep "simrs-vx/internal/domain/main-entities/person"
	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"
	esimgos "simrs-vx/internal/domain/simgos-entities/m-pasien"
	esyncLog "simrs-vx/internal/domain/sync-entities/log"
	esync "simrs-vx/internal/domain/sync-entities/patient"

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

func setDataSimgos(input *e.Patient) (data esimgos.MPasien) {
	if p := input.Person; p != nil {
		mapPerson(p, &data)
	}

	if input.Person != nil && input.Person.Addresses != nil {
		mapAddress(input.Person.Addresses, &data)
	}

	if input.Person != nil && input.Person.Contacts != nil {
		mapContact(input.Person.Contacts, &data)
	}

	if input.Person != nil {
		data.PenanggungjawabHubungan = "DIRI SENDIRI"
		data.PenanggungjawabNama = input.Person.Name
		data.PenanggungjawabPhone = data.Notelp
		data.PenanggungjawabAlamat = data.Alamat

		if input.Person.Relatives != nil {
			mapRelative(input.Person.Relatives, &data)
		}
	}

	data.Nip = input.RegisteredBy_User_Name
	data.Tgldaftar = input.RegisteredAt
	data.Nomr = *input.Number

	nomrBaru, _ := generateNomrPatient("00")
	data.NomrBaru = &nomrBaru

	return
}

func generateNomrPatient(prefix string) (string, error) {
	const maxSuffix = 999999

	column := "nomr"
	if prefix == "00" {
		column = "nomr_baru"
	}

	type Row struct {
		Value string `gorm:"column:value"`
	}

	var r Row

	// Lock rows for this prefix → prevents race condition
	if err := dg.IS["simrs"].
		Table("m_pasien").
		Select(fmt.Sprintf("%s AS value", column)).
		Where(fmt.Sprintf("%s LIKE ?", column), prefix+"%").
		Order(fmt.Sprintf("%s DESC", column)).
		Clauses(clause.Locking{Strength: "UPDATE"}).
		Limit(1).
		Scan(&r).Error; err != nil {
		return "", err
	}

	// No existing number → start at 000001
	if r.Value == "" || len(r.Value) < len(prefix) {
		return prefix + "000001", nil
	}

	suffix := r.Value[len(prefix):]
	num, err := strconv.Atoi(suffix)
	if err != nil {
		// data corrupt, reset nomor
		return prefix + "000001", nil
	}

	// If reached 999999 → increment prefix
	if num >= maxSuffix {
		p, _ := strconv.Atoi(prefix)
		p++
		return fmt.Sprintf("%02d000001", p), nil
	}

	// Normal increment
	return prefix + fmt.Sprintf("%06d", num+1), nil
}

func mapPerson(p *ep.Person, data *esimgos.MPasien) {
	if p.FrontTitle != nil {
		data.Title = *p.FrontTitle
	}

	if p.BirthRegency != nil {
		data.Tempat = p.BirthRegency.Name
	}

	if p.BirthDate != nil {
		data.Tgllahir = p.BirthDate
	}

	if p.Gender_Code != nil {
		if *p.Gender_Code == erp.GCMale {
			data.Jeniskelamin = "L"
		} else if *p.Gender_Code == erp.GCFemale {
			data.Jeniskelamin = "P"
		}
	}

	data.Nama = p.Name
	data.Noktp = *p.ResidentIdentityNumber
	data.NoktpBaru = *p.ResidentIdentityNumber
	data.Pekerjaan = *p.Ocupation_Name

	data.Status, data.TxtStatus = setMaritalStatus(p.MaritalStatus_Code)
	data.Agama, data.TxtAgama = setReligion(p.Religion_Code)
	data.Pendidikan, data.TxtPendidikan = setEducation(p.Education_Code)

	if c := p.VclaimMember; c != nil && c.CardNumber != nil {
		data.NoKartu = *c.CardNumber
	}

	if l := p.Language; l != nil {
		data.Bahasa = l.Name
	}

	if p.CommunicationIssueStatus {
		data.HambatanKomunikasi = "Y"
	} else {
		data.HambatanKomunikasi = "T"
	}

	if p.Nationality != nil {
		data.Kebangsaan = *p.Nationality
	}

	if p.Ethnic != nil {
		data.Suku = p.Ethnic.Name
	}
}

func setMaritalStatus(code *erp.MaritalStatusCode) (uint, string) {
	if code != nil {
		switch *code {
		case erp.MaritalStatusSingle:
			return 1, "Belum Menikah"
		case erp.MaritalStatusMarried:
			return 2, "Menikah"
		case erp.MaritalStatusDivorced:
			return 4, "Cerai"
		case erp.MaritalStatusWidowed:
			return 3, "Janda/Duda"
		default:
			return 0, "Tidak Diketahui"
		}
	} else {
		return 0, "Tidak Diketahui"
	}
}

func setReligion(code *erp.ReligionCode) (uint, string) {
	if code != nil {
		switch *code {
		case erp.RCIslam:
			return 1, "Islam"
		case erp.RCProtestan:
			return 2, "Kristen Protestan"
		case erp.RCKatolik:
			return 3, "Katholik"
		case erp.RCHindu:
			return 4, "Hindu"
		case erp.RCBudha:
			return 5, "Budha"
		case erp.RCKonghucu:
			return 6, "Konghucu"
		default:
			return 9, "Lainnya"
		}
	} else {
		return 0, "Tidak Diketahui"
	}
}

func setEducation(code *erp.EducationCode) (uint, string) {
	if code != nil {
		switch *code {
		case erp.ECTS:
			return 0, "Tidak Sekolah"
		case erp.ECSD:
			return 1, string(erp.ECSD)
		case erp.ECSLTP:
			return 2, string(erp.ECSLTP)
		case erp.ECSLTA:
			return 3, string(erp.ECSLTA)
		case erp.ECD1, erp.ECD2, erp.ECD3, erp.ECD4:
			return 4, "D3/Akademik"
		case erp.ECS1, erp.ECS2, erp.ECS3:
			return 5, "Universitas"
		case erp.ECOther:
			return 6, "Lainnya"
		default:
			return 7, "Tidak Diketahui"
		}
	} else {
		return 7, "Tidak Diketahui"
	}
}

func mapAddress(addresses *[]epa.PersonAddress, data *esimgos.MPasien) {
	if addresses == nil || len(*addresses) == 0 {
		return
	}

	a := (*addresses)[0]
	data.Alamat = a.Address
	data.AlamatKtp = a.Address

	if v := a.Village; v != nil {
		villageCode, _ := strconv.Atoi(v.Code)
		data.Kelurahan = uint64(villageCode)
		data.TxtKelurahan = v.Name

		if d := v.District; d != nil {
			districtCode, _ := strconv.Atoi(d.Code)
			data.Kdkecamatan = uint(districtCode)
			data.TxtKecamatan = d.Name

			if r := d.Regency; r != nil {
				regencyCode, _ := strconv.Atoi(r.Code)
				data.Kota = uint(regencyCode)
				data.TxtKota = r.Name

				if p := r.Province; p != nil {
					provinceCode, _ := strconv.Atoi(p.Code)
					data.Kdprovinsi = uint(provinceCode)
					data.TxtProvinsi = p.Name
				}
			}
		}
	}
}

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

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

}

func mapRelative(relative *[]epr.PersonRelative, data *esimgos.MPasien) {
	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"
				switch {
				case r.Relationship_Code == erp.RCMother:
					data.NamaIbu = *r.Name
					eduCode, _ := setEducation(r.Education_Code)
					data.PendidikanIbu = strconv.Itoa(int(eduCode))
				case r.Relationship_Code == erp.RCFather:
					data.NamaAyah = *r.Name
					eduCode, _ := setEducation(r.Education_Code)
					data.PendidikanAyah = strconv.Itoa(int(eduCode))
				}
			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.PatientSimxLog) {
	// 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.PatientLink) {
	data.Simx_Id = simxId
	data.Simgos_Id = simgosId
	return
}
