package generatefile

import (
	"encoding/json"
	"errors"
	"fmt"
	"mime"
	"path/filepath"
	"time"

	evs "simrs-vx/internal/domain/bpjs-entities/vclaim-sep"
	evscl "simrs-vx/internal/domain/bpjs-entities/vclaim-sep-control-letter"
	ee "simrs-vx/internal/domain/main-entities/encounter"
	egc "simrs-vx/internal/domain/main-entities/general-consent"
	er "simrs-vx/internal/domain/main-entities/resume"
	es "simrs-vx/internal/domain/main-entities/screening"

	uvs "simrs-vx/internal/use-case/bpjs-use-case/vclaim-sep"
	uvscl "simrs-vx/internal/use-case/bpjs-use-case/vclaim-sep-control-letter"
	ue "simrs-vx/internal/use-case/main-use-case/encounter"
	ugc "simrs-vx/internal/use-case/main-use-case/general-consent"
	ur "simrs-vx/internal/use-case/main-use-case/resume"
	us "simrs-vx/internal/use-case/main-use-case/screening"

	ercl "simrs-vx/internal/domain/references/clinical"
	erc "simrs-vx/internal/domain/references/common"
	docscfg "simrs-vx/internal/infra/docs-cfg"

	pc "simrs-vx/pkg/conv-helper"
	pf "simrs-vx/pkg/file-helper"
	pl "simrs-vx/pkg/logger"
	pm "simrs-vx/pkg/minio-helper"
	pp "simrs-vx/pkg/pdf-helper"
	pu "simrs-vx/pkg/use-case-helper"

	"gorm.io/gorm"
)

// generate temporary file, upload to minio, generate public url, delete temporary file
func generateFile(input GenerateDto, templateData any) (string, error) {
	newPath, err := pf.PathToSaveFile(fmt.Sprintf("./temporary/%s", input.EntityType_Code))
	if err != nil {
		return "", err
	}

	fPath := fmt.Sprintf("%s/%s-%s.%s", newPath, input.Type_Code, time.Now().Format("20060102150405"), input.FormatType)

	templatePath := docscfg.O.GetPath() + string(input.TemplateName)

	switch input.FormatType {
	case erc.DFTCPDF:
		if err := generatePDF(GeneratePDFdto{
			TemplatePath:  templatePath,
			TemplateData:  templateData,
			PdfPath:       fPath,
			UseA5Lanscape: input.UseA5Lanscape,
		}); err != nil {
			return "", err
		}
	case erc.DFTCTXLSX:
		// TODO: generate xlsx
	case erc.DFTCTCSV:
		// TODO: generate csv
	default:
		return "", errors.New("invalid format type")
	}

	bucketName := input.EntityType_Code

	objectName := fmt.Sprintf("%s/%s-%d.%s", *pc.UintToString(input.Encounter_Id), input.Type_Code, time.Now().UnixNano(), input.FormatType)
	pdfUpload := pm.UploadPathInput{
		BucketName:  string(bucketName),
		Name:        objectName,
		Path:        fPath,
		ContentType: mime.TypeByExtension(filepath.Ext(fPath)),
	}

	// create bucket if not exist, create object in bucket
	info, err := pm.I.FPutObject(pdfUpload)
	if err != nil {
		return "", err
	}

	// generate public url
	urlPub := pm.I.GenerateUrl(info.Bucket, info.Key)
	if err := pf.DeleteFolder(fPath); err != nil {
		return "", err
	}

	return urlPub, nil
}

func generatePDF(input GeneratePDFdto) error {
	// parse template data into html template
	r := pp.NewRequestPdf("")
	if err := r.ParseTemplate(input.TemplatePath, input.TemplateData); err == nil {
		_, err := r.GenerateByCommand(input.PdfPath, input.TemplatePath, input.UseA5Lanscape)
		if err != nil {
			return errors.New("generate pdf by command error : " + err.Error())
		}
	} else {
		return errors.New("parse template error : " + err.Error())
	}

	return nil
}

func removeFile(bucket, fileUrl string) error {
	fPath := pu.GetLastTwoPathSegments(fileUrl)
	err := pm.I.RemoveObject(bucket, fPath)
	if err != nil {
		return err
	}
	return nil
}

// generate general consent
func generateGC(input GenerateDto, event pl.Event, tx *gorm.DB) (*ResponseDto, error) {
	// get value from general consent by ref_id
	gc, err := ugc.ReadDetailData(egc.ReadDetailDto{Id: uint(*pc.StringToUint64(*input.Ref_Id))}, &event)
	if err != nil {
		return nil, err
	}

	if gc.FileUrl != nil {
		if err := removeFile(string(input.EntityType_Code), *gc.FileUrl); err != nil {
			return nil, err
		}
	}

	// map template data
	templateData := GeneralConsentPDF{}
	if gc.Value != nil {
		err := json.Unmarshal([]byte(*gc.Value), &templateData)
		if err != nil {
			event.ErrInfo = pl.ErrorInfo{
				Code:   "data-unmarshal-fail",
				Detail: err.Error(),
				Raw:    err,
			}
			return nil, err
		}
	} else {
		return nil, errors.New("no value in this general consent")
	}

	input.FormatType = erc.DFTCPDF
	input.TemplateName = TDNGC
	input.Encounter_Id = gc.Encounter_Id
	templateData.Date = pu.FormatIndonesianDate(gc.CreatedAt)

	// generate file
	urlPub, err := generateFile(input, templateData)
	if err != nil {
		return nil, err
	}

	gc.FileUrl = &urlPub
	if err := tx.Save(&gc).Error; err != nil {
		return nil, err
	}

	response := ResponseDto{
		FileUrl: urlPub,
	}

	return &response, nil
}

func generateCL(input GenerateDto, event pl.Event, tx *gorm.DB) (*ResponseDto, error) {
	// get value from control letter by ref_id
	cl, err := uvscl.ReadDetailData(evscl.ReadDetailDto{Number: input.Ref_Id}, &event)
	if err != nil {
		if !pu.IsDataNotFoundError(err) {
			return nil, err
		}
	}

	if cl != nil && cl.FileUrl != nil {
		if err := removeFile(string(input.EntityType_Code), *cl.FileUrl); err != nil {
			return nil, err
		}
	}

	// map template data
	clData := VclaimControlLetter{}
	if input.Data != nil {
		err := json.Unmarshal([]byte(*input.Data), &clData)
		if err != nil {
			event.ErrInfo = pl.ErrorInfo{
				Code:   "data-unmarshal-fail",
				Detail: err.Error(),
				Raw:    err,
			}
			return nil, err
		}
	} else {
		return nil, errors.New("there is no data to be used")
	}

	if cl == nil {
		createCL := evscl.CreateDto{
			VclaimSep_Number: &clData.VclaimSep.Number,
			Number:           &clData.Number,
			Value:            input.Data,
		}
		if cl, err = uvscl.CreateData(createCL, &event, tx); err != nil {
			return nil, err
		}

	}
	// get encounter id by vclaim sep number
	vs, err := uvs.ReadDetailData(evs.ReadDetailDto{Number: &clData.VclaimSep.Number}, &event)
	if err != nil {
		return nil, err
	}

	input.FormatType = erc.DFTCPDF
	input.TemplateName = TDNCL
	input.Encounter_Id = vs.Encounter_Id
	input.UseA5Lanscape = true

	templateData := clData.generateTemplateData()
	// generate file
	urlPub, err := generateFile(input, templateData)
	if err != nil {
		return nil, err
	}

	cl.FileUrl = &urlPub
	if err := tx.Save(&cl).Error; err != nil {
		return nil, err
	}

	response := ResponseDto{
		FileUrl: urlPub,
	}

	return &response, nil
}

func generateResume(input GenerateDto, event pl.Event, tx *gorm.DB) (*ResponseDto, error) {
	// get value from resume by ref_id
	includes := "Doctor.Employee.Person"
	r, err := ur.ReadDetailData(er.ReadDetailDto{Id: uint(*pc.StringToUint64(*input.Ref_Id)), Includes: includes}, &event)
	if err != nil {
		return nil, err
	}

	if !r.IsValidated() {
		return nil, errors.New("resume is not validated")
	}

	if r.FileUrl != nil {
		if err := removeFile(string(input.EntityType_Code), *r.FileUrl); err != nil {
			return nil, err
		}
	}

	templateData, err := generateResumeTemplateData(*r, event, tx)
	if err != nil {
		return nil, err
	}

	input.FormatType = erc.DFTCPDF
	input.TemplateName = TDNR
	input.Encounter_Id = r.Encounter_Id

	// generate file
	urlPub, err := generateFile(input, templateData)
	if err != nil {
		return nil, err
	}

	r.FileUrl = &urlPub
	if err := tx.Save(&r).Error; err != nil {
		return nil, err
	}

	response := ResponseDto{
		FileUrl: urlPub,
	}

	return &response, nil
}

func generateResumeTemplateData(resume er.Resume, event pl.Event, tx *gorm.DB) (*ResumePDF, error) {
	// get encounter
	includes := "Patient,Patient.Person,Patient.Person.BirthRegency,Patient.Person.Contacts"
	encounter, err := ue.ReadDetailData(ee.ReadDetailDto{Id: *resume.Encounter_Id, Includes: includes}, &event)
	if err != nil {
		return nil, err
	}

	// map template data
	rData := er.ValueDto{}
	if resume.Value != nil {
		err := json.Unmarshal([]byte(*resume.Value), &rData)
		if err != nil {
			event.ErrInfo = pl.ErrorInfo{
				Code:   "data-unmarshal-fail",
				Detail: err.Error(),
				Raw:    err,
			}
			return nil, err
		}
	} else {
		return nil, errors.New("there is no data to be used")
	}
	templateData := ResumePDF{}
	templateData.MedicalRecord = *encounter.Patient.Number
	templateData.NIK = *encounter.Patient.Person.ResidentIdentityNumber
	templateData.Name = encounter.Patient.Person.Name
	templateData.BirthPlaceDate = pu.FormatPlaceAndDate(encounter.Patient.Person.BirthRegency.Name, *encounter.Patient.Person.BirthDate)
	templateData.Gender = encounter.Patient.Person.GenderString()
	templateData.Phone = encounter.Patient.Person.GetPhoneNumber()
	// templateData.Class = get from vclaim sep eg. III
	// templateData.Unit = get from vclaim sep eg. R.KERINCI - KLS 3
	templateData.Doctor_Name = resume.Doctor.Employee.Person.FullName()
	templateData.StartedAt = *pc.TimeToStringWithFormat(encounter.StartedAt, "")
	templateData.FinishedAt = *pc.TimeToStringWithFormat(encounter.FinishedAt, "")
	templateData.DiagnosisIn = rData.Assessment.DiagnosisIn
	templateData.Diagnosis = rData.Diagnosis.SecondaryDiagnosis
	templateData.MainComplaint = rData.Assessment.MainComplaint
	templateData.MedicalHistory = rData.Assessment.MedicalHistory
	templateData.PhysicalExamination = rData.Assessment.PhysicalExamination
	templateData.SupportingExamination = rData.Supporting.Notes
	templateData.MedicalActions = rData.Action.AdditionalActions
	templateData.MedicalAction = rData.Action.MedicalActions
	templateData.Consultations = rData.Consultation.Consultations
	templateData.Allergy = rData.Pharmacy.AllergySpecialConditions
	templateData.ConsciousnessLevel = rData.Discharge.ConsciousnessLevel
	templateData.BloodPressure = fmt.Sprintf("%.0f/%.0f", rData.Discharge.BloodPressureSystolic, rData.Discharge.BloodPressureDiastolic)
	templateData.BodyTemperature = rData.Discharge.BodyTemperature
	templateData.RespirationRate = rData.Discharge.RespirationRate
	templateData.HeartRate = rData.Discharge.HeartRate
	templateData.PainScale = rData.Discharge.PainScale
	templateData.ConditionOnDischarge = rData.Management.ConditionOnDischarge
	templateData.DischargeMethod = rData.Management.DischargeMethod
	templateData.Medications = rData.Medication.Medications
	templateData.Date = pu.GetTimeNow().Format("02-01-2006")

	return &templateData, nil
}

func generateScreeningTemplateData(screenings []es.Screening, event pl.Event, tx *gorm.DB) (*ScreeningPDF, error) {
	// get encounter
	includes := "Patient,Patient.Person"
	encounter, err := ue.ReadDetailData(ee.ReadDetailDto{Id: *screenings[0].Encounter_Id, Includes: includes}, &event)
	if err != nil {
		return nil, err
	}

	switch screenings[0].Type {
	case ercl.SFTCA:
		// map template data
		sData := es.FormA{}
		if screenings[0].Value != nil {
			err := json.Unmarshal([]byte(*screenings[0].Value), &sData)
			if err != nil {
				event.ErrInfo = pl.ErrorInfo{
					Code:   "data-unmarshal-fail",
					Detail: err.Error(),
					Raw:    err,
				}
				return nil, err
			}
		} else {
			return nil, errors.New("there is no data to be used")
		}

		templateData := ScreeningPDF{}
		templateData.IssuedDate = pu.GetTimeNow().Format("2006-01-02 15:04:05")
		templateData.MedicalRecord = *encounter.Patient.Number
		templateData.Name = encounter.Patient.Person.FullName()
		templateData.BirthDate = encounter.Patient.Person.BirthDate.Format("2006-01-02 15:04:05")
		templateData.Employee_Name = screenings[0].Employee.Person.FullName()
		templateData.EarlyMedic = sData.Screening.SelectedScreeningLabels()
		templateData.Assessment = sData.AssessmentDetail
		templateData.ProblemIdentification = sData.ProblemIdentification.SelectedProblemLabels()
		templateData.Planning = sData.PlanningDetail
		templateData.Date = screenings[0].CreatedAt.Format("2006-01-02 15:04:05")

		return &templateData, nil
	case ercl.SFTCB:
		// map template data
		templateData := ScreeningPDF{}
		templateData.IssuedDate = pu.GetTimeNow().Format("2006-01-02 15:04:05")
		templateData.MedicalRecord = *encounter.Patient.Number
		templateData.Name = encounter.Patient.Person.FullName()
		templateData.BirthDate = encounter.Patient.Person.BirthDate.Format("2006-01-02 15:04:05")
		for k, v := range screenings {
			templateData.FormB = append(templateData.FormB, ScreeningFormBPDF{
				Number:        k + 1,
				Date:          v.CreatedAt.Format("2006-01-02 15:04:05"),
				Employee_Name: v.Employee.Person.FullName(),
				Value:         *v.Value,
			})
		}

		return &templateData, nil
	}
	return nil, errors.New("invalid screening type")
}

func generateScreening(input GenerateDto, event pl.Event, tx *gorm.DB) (*ResponseDto, error) {
	// get value from resume by ref_id
	includes := "Employee.Person"
	s, err := us.ReadDetailData(es.ReadDetailDto{Id: uint(*pc.StringToUint64(*input.Ref_Id)), Includes: includes}, &event)
	if err != nil {
		return nil, err
	}

	var screenings []es.Screening
	var templateData *ScreeningPDF
	if s.IsFormA() {
		if s.FileUrl != nil {
			if err := removeFile(string(input.EntityType_Code), *s.FileUrl); err != nil {
				return nil, err
			}
		}
		screenings = append(screenings, *s)
		input.FormatType = erc.DFTCPDF
		input.TemplateName = TDNSA
		input.Encounter_Id = s.Encounter_Id
		templateData, err = generateScreeningTemplateData(screenings, event, tx)
		if err != nil {
			return nil, err
		}
	} else {
		includes := "Employee.Person"
		sort := "CreatedAt:ASC"
		ss, _, err := us.ReadListData(es.ReadListDto{FilterDto: es.FilterDto{Encounter_Id: s.Encounter_Id, Type: ercl.SFTCB}, Includes: includes, Sort: sort}, &event, tx)
		if err != nil {
			return nil, err
		}
		screenings = append(screenings, ss...)
		input.FormatType = erc.DFTCPDF
		input.TemplateName = TDNSB
		input.Encounter_Id = s.Encounter_Id
		templateData, err = generateScreeningTemplateData(screenings, event, tx)
		if err != nil {
			return nil, err
		}
	}

	// generate file
	urlPub, err := generateFile(input, templateData)
	if err != nil {
		return nil, err
	}

	if len(screenings) > 1 {
		for i := range screenings {
			screenings[i].FileUrl = &urlPub
		}
		if err := tx.Save(&screenings).Error; err != nil {
			return nil, err
		}
	} else {
		s.FileUrl = &urlPub
		if err := tx.Save(&s).Error; err != nil {
			return nil, err
		}
	}

	response := ResponseDto{
		FileUrl: urlPub,
	}

	return &response, nil
}
