package platform

import (
	"fmt"
	"time"

	"github.com/aarondl/opt/null"
	"github.com/google/uuid"
	"github.com/rs/zerolog/log"
	"github.com/uber/h3-go/v4"
	"source.gleipnir.technology/Gleipnir/nidus-sync/db/models"
	"source.gleipnir.technology/Gleipnir/nidus-sync/db/sql"
	"source.gleipnir.technology/Gleipnir/nidus-sync/h3utils"
)

type BreedingSourceDetail struct {
	// Basic Information
	OrganizationID int32     `json:"organizationId"`
	Name           string    `json:"name"`
	Description    string    `json:"description"`
	LocationNumber int64     `json:"locationNumber"`
	ObjectID       int64     `json:"objectId"`
	GlobalID       uuid.UUID `json:"globalId"`
	ExternalID     string    `json:"externalId"`

	// Status Information
	Active           bool   `json:"active"`
	DeactivateReason string `json:"deactivateReason"`
	SourceStatus     string `json:"sourceStatus"`
	Priority         string `json:"priority"`
	ScalarPriority   int64  `json:"scalarPriority"`

	// Classification
	SourceType  string `json:"sourceType"`
	Habitat     string `json:"habitat"`
	UseType     string `json:"useType"`
	WaterOrigin string `json:"waterOrigin"`
	Symbology   string `json:"symbology"`

	// Geographical Data
	H3Cell            h3.Cell `json:"h3cell"`
	Zone              string  `json:"zone"`
	Zone2             string  `json:"zone2"`
	Jurisdiction      string  `json:"jurisdiction"`
	AccessDescription string  `json:"accessDescription"`

	// Inspection Data
	LarvaeInspectInterval       int16      `json:"larvaeInspectInterval"`
	LastInspectionDate          *time.Time `json:"lastInspectionDate"`
	LastInspectionActivity      string     `json:"lastInspectionActivity"`
	LastInspectionActionTaken   string     `json:"lastInspectionActionTaken"`
	LastInspectionAverageLarvae float64    `json:"lastInspectionAverageLarvae"`
	LastInspectionAveragePupae  float64    `json:"lastInspectionAveragePupae"`
	LastInspectionBreeding      string     `json:"lastInspectionBreeding"`
	LastInspectionConditions    string     `json:"lastInspectionConditions"`
	LastInspectionFieldSpecies  string     `json:"lastInspectionFieldSpecies"`
	LastInspectionLifeStages    string     `json:"lastInspectionLifeStages"`

	// Treatment Data
	LastTreatmentDate         *time.Time `json:"lastTreatmentDate"`
	LastTreatmentActivity     string     `json:"lastTreatmentActivity"`
	LastTreatmentProduct      string     `json:"lastTreatmentProduct"`
	LastTreatmentQuantity     float64    `json:"lastTreatmentQuantity"`
	LastTreatmentQuantityUnit string     `json:"lastTreatmentQuantityUnit"`

	// Assignment & Schedule
	AssignedTechnician      string     `json:"assignedTechnician"`
	NextActionScheduledDate *time.Time `json:"nextActionScheduledDate"`

	// Metadata
	Created  *time.Time `json:"created"`
	Creator  string     `json:"creator"`
	EditedAt *time.Time `json:"editedAt"`
	Editor   string     `json:"editor"`
	Comments string     `json:"comments"`
}

type BreedingSourceSummary struct {
	ID            uuid.UUID
	Type          string
	LastInspected *time.Time
	LastTreated   *time.Time
}

type Trap struct {
	Active      bool
	Comments    string
	Collections []TrapData
	Description string
	GlobalID    uuid.UUID
	H3Cell      h3.Cell
}

type TrapCount struct {
	Ended   time.Time
	Females int
	ID      uuid.UUID
	Males   int
	Total   int
}

type TrapData struct {
	// Basic Identifiers
	OrganizationID int32     `json:"organizationId"`
	ObjectID       int64     `json:"objectId"`
	GlobalID       uuid.UUID `json:"globalId"`
	LocationName   string    `json:"locationName"`
	LocationID     uuid.UUID `json:"locationId"`
	SRID           uuid.UUID `json:"srid"`
	Field          int64     `json:"field"`

	// Trap Information
	TrapType         string `json:"trapType"`
	TrapCondition    string `json:"trapCondition"`
	TrapActivityType string `json:"trapActivityType"`
	TrapNights       int16  `json:"trapNights"`
	Lure             string `json:"lureType"`

	// Personnel
	FieldTechnician        string `json:"fieldTechnician"`
	IdentifiedByTechnician string `json:"identifiedByTechnician"`
	SortedByTechnician     string `json:"sortedByTechnician"`

	// Timing
	StartDateTime *time.Time `json:"startDateTime"`
	EndDateTime   *time.Time `json:"endDateTime"`

	// Environmental Conditions
	AverageTemperature float64 `json:"averageTemperature"`
	Rainfall           float64 `json:"rainfall"`
	WindDirection      string  `json:"windDirection"`
	WindSpeed          float64 `json:"windSpeed"`
	SiteCondition      string  `json:"siteCondition"`

	// Status and Processing
	Processed     bool       `json:"processed"`
	RecordStatus  int16      `json:"recordStatus"`
	Reviewed      bool       `json:"reviewed"`
	ReviewedBy    string     `json:"reviewedBy"`
	ReviewedDate  *time.Time `json:"reviewedDate"`
	GatewaySynced bool       `json:"gatewaySynced"`
	LR            bool       `json:"laboratoryReported"`
	Voltage       float64    `json:"voltage"`

	// Location Data
	H3Cell h3.Cell `json:"h3cell"`
	Zone   string  `json:"zone"`
	Zone2  string  `json:"zone2"`

	// Vector Survey IDs
	VectorSurveyTrapDataID     string `json:"vectorSurveyTrapDataId"`
	VectorSurveyTrapLocationID string `json:"vectorSurveyTrapLocationId"`

	// Metadata
	Created        *time.Time `json:"created"`
	Creator        string     `json:"creator"`
	CreatedByUser  string     `json:"createdByUser"`
	CreatedDateAlt *time.Time `json:"createdDateAlt"`
	Edited         *time.Time `json:"edited"`
	Editor         string     `json:"editor"`
	LastEditedDate *time.Time `json:"lastEditedDate"`
	LastEditedUser string     `json:"lastEditedUser"`
	Comments       string     `json:"comments"`

	// Stuff I actually use
	Count TrapCount
}

type TrapNearby struct {
	Counts   []*TrapCount
	Distance string
	ID       uuid.UUID
}

type TrapSummary struct {
	Active      bool
	Comments    string
	Description string
	GlobalID    uuid.UUID
}

type Treatment struct {
	CadenceDelta time.Duration
	Date         *time.Time
	LocationID   uuid.UUID
	Notes        string
	Product      string
}

func toTrap(trap *models.FieldseekerTraplocation, trap_data []sql.TrapDataByLocationIDRecentRow, count_slice []sql.TrapCountByLocationIDRow) (result Trap, err error) {
	log.Debug().Str("globalid", trap.Globalid.String()).Msg("Working on trap")
	cell, err := h3utils.ToCell(trap.H3cell.MustGet())
	if err != nil {
		return result, fmt.Errorf("Failed to convert h3 cell: %w", err)
	}

	count_by_trapdata_id := make(map[uuid.UUID]TrapCount, 0)
	for _, count := range count_slice {
		count_by_trapdata_id[count.TrapdataGlobalid] = TrapCount{
			Ended:   count.TrapdataEnddate.MustGet(),
			Females: int(count.TotalFemales),
			Males:   int(count.TotalMales),
			Total:   int(count.Total),
		}
	}

	data_by_id := make(map[uuid.UUID]TrapData, 0)
	for _, dt := range trap_data {
		if dt.LocID != trap.Globalid {
			return result, fmt.Errorf("Bad query")
		}
		log.Debug().Str("trapdata", dt.Globalid.String()).Msg("Aggregating trapdata")
		count, ok := count_by_trapdata_id[dt.Globalid]
		if !ok {
			count = TrapCount{}
		}
		data_by_id[dt.Globalid] = TrapData{
			Count:       count,
			EndDateTime: &dt.Enddatetime,
			GlobalID:    dt.Globalid,
		}
	}
	data := make([]TrapData, 0)
	for _, v := range data_by_id {
		data = append(data, v)
	}

	return Trap{
		Active:      toBool16Or(trap.Active, false),
		Comments:    trap.Comments.GetOr(""),
		Collections: data,
		Description: trap.Description.GetOr(""),
		GlobalID:    trap.Globalid,
		H3Cell:      cell,
	}, nil
}

func toTemplateTrapSummary(traps models.FieldseekerTraplocationSlice) (results []TrapSummary, err error) {
	for _, t := range traps {
		results = append(results, TrapSummary{
			Active:      toBool16Or(t.Active, false),
			Comments:    t.Comments.GetOr(""),
			Description: t.Description.GetOr(""),
			GlobalID:    t.Globalid,
		})
	}
	return results, err
}
func toTemplateTrapsNearby(locations []sql.TrapLocationBySourceIDRow, trap_data []sql.TrapDataByLocationIDRecentRow, counts []sql.TrapCountByLocationIDRow) ([]TrapNearby, error) {
	results := make([]TrapNearby, 0)
	count_by_trap_data_id := make(map[uuid.UUID]*sql.TrapCountByLocationIDRow)
	for _, c := range counts {
		count_by_trap_data_id[c.TrapdataGlobalid] = &c
	}
	counts_by_location_id := make(map[uuid.UUID][]*TrapCount)
	for _, td := range trap_data {
		c, ok := count_by_trap_data_id[td.Globalid]
		if !ok {
			return results, fmt.Errorf("Failed to find trap count for %s", td.Globalid)
		}
		loc_id := td.LocID
		count := &TrapCount{
			Ended:   td.Enddatetime,
			Females: int(c.TotalFemales),
			ID:      td.Globalid,
			Males:   int(c.TotalMales),
			Total:   int(c.Total),
		}
		counts, ok := counts_by_location_id[loc_id]
		if !ok {
			counts = []*TrapCount{count}
		} else {
			counts = append(counts, count)
		}
		counts_by_location_id[loc_id] = counts
	}
	for _, location := range locations {
		counts, ok := counts_by_location_id[location.TrapLocationGlobalid]
		if !ok {
			return results, fmt.Errorf("Failed to find counts for %s", location.TrapLocationGlobalid)
		}
		trap := TrapNearby{
			Counts:   counts,
			Distance: location.Distance,
			ID:       location.TrapLocationGlobalid,
		}
		results = append(results, trap)
	}
	return results, nil
}

func toTreatment(rows models.FieldseekerTreatmentSlice) ([]Treatment, error) {
	var results []Treatment
	for _, r := range rows {
		results = append(results, Treatment{
			Date:       getTimeOrNull(r.Enddatetime),
			LocationID: r.Pointlocid.GetOr(uuid.UUID{}),
			Notes:      r.Comments.GetOr("none"),
			Product:    r.Product.GetOr("none"),
		})
	}
	return results, nil
}

func toTemplateInspection(rows models.FieldseekerMosquitoinspectionSlice) ([]Inspection, error) {
	var results []Inspection
	for _, r := range rows {
		results = append(results, Inspection{
			Action:     r.Actiontaken.GetOr("none"),
			Date:       getTimeOrNull(r.Enddatetime),
			Notes:      r.Comments.GetOr("none"),
			Location:   r.Locationname.GetOr("none"),
			LocationID: r.Pointlocid.GetOr(uuid.UUID{}),
		})
	}
	return results, nil
}

// Helper function to convert unix timestamp to time.Time
func fsIntToBool(val null.Val[int16]) bool {
	if !val.IsValue() {
		return false
	}
	b := val.MustGet() != 0
	return b
}

// toTemplateBreedingSource transforms the DB model into the display model
func toBreedingSource(source *models.FieldseekerPointlocation) (*BreedingSourceDetail, error) {
	if source.H3cell.IsNull() {
		return nil, fmt.Errorf("h3 cell is null")
	}
	cell, err := h3utils.ToCell(source.H3cell.MustGet())
	if err != nil {
		return nil, fmt.Errorf("Failed to get h3 cell from point location: %w", err)
	}
	return &BreedingSourceDetail{
		// Basic Information
		OrganizationID: source.OrganizationID,
		Name:           source.Name.MustGet(),
		Description:    source.Description.MustGet(),
		LocationNumber: int64(source.Locationnumber.GetOr(0)),
		ObjectID:       source.Objectid,
		GlobalID:       source.Globalid,
		ExternalID:     source.Externalid.GetOr(""),

		// Status Information
		Active:           fsIntToBool(source.Active),
		DeactivateReason: source.DeactivateReason.GetOr(""),
		SourceStatus:     source.Sourcestatus.GetOr(""),
		Priority:         source.Priority.GetOr(""),
		ScalarPriority:   int64(source.Scalarpriority.GetOr(0)),

		// Classification
		SourceType:  source.Stype.GetOr(""),
		Habitat:     source.Habitat.GetOr(""),
		UseType:     source.Usetype.GetOr(""),
		WaterOrigin: source.Waterorigin.GetOr(""),
		Symbology:   source.Symbology.GetOr(""),

		// Geographical Data
		H3Cell:            cell,
		Zone:              source.Zone.GetOr(""),
		Zone2:             source.Zone2.GetOr(""),
		Jurisdiction:      source.Jurisdiction.GetOr(""),
		AccessDescription: source.Accessdesc.GetOr(""),

		// Inspection Data
		LarvaeInspectInterval:       source.Larvinspectinterval.GetOr(0),
		LastInspectionDate:          getTimeOrNull(source.Lastinspectdate),
		LastInspectionActivity:      source.Lastinspectactivity.GetOr(""),
		LastInspectionActionTaken:   source.Lastinspectactiontaken.GetOr(""),
		LastInspectionAverageLarvae: source.Lastinspectavglarvae.GetOr(0),
		LastInspectionAveragePupae:  source.Lastinspectavgpupae.GetOr(0),
		LastInspectionBreeding:      source.Lastinspectbreeding.GetOr(""),
		LastInspectionConditions:    source.Lastinspectconditions.GetOr(""),
		LastInspectionFieldSpecies:  source.Lastinspectfieldspecies.GetOr(""),
		LastInspectionLifeStages:    source.Lastinspectlstages.GetOr(""),

		// Treatment Data
		LastTreatmentDate:         getTimeOrNull(source.Lasttreatdate),
		LastTreatmentActivity:     source.Lasttreatactivity.GetOr(""),
		LastTreatmentProduct:      source.Lasttreatproduct.GetOr(""),
		LastTreatmentQuantity:     source.Lasttreatqty.GetOr(0),
		LastTreatmentQuantityUnit: source.Lasttreatqtyunit.GetOr(""),

		// Assignment & Schedule
		AssignedTechnician:      source.Assignedtech.GetOr(""),
		NextActionScheduledDate: getTimeOrNull(source.Nextactiondatescheduled),

		// Metadata
		Created:  getTimeOrNull(source.Creationdate),
		Creator:  source.Creator.GetOr(""),
		EditedAt: getTimeOrNull(source.Editdate),
		Editor:   source.Editor.GetOr(""),
		Comments: source.Comments.GetOr(""),
	}, nil
}

func getTimeOrNull(v null.Val[time.Time]) *time.Time {
	if v.IsNull() {
		return nil
	}
	val := v.MustGet()
	return &val
}

func toBool16Or(t null.Val[int16], def bool) bool {
	if t.IsNull() {
		return def
	}
	val := t.MustGet()
	var b bool
	if val == 0 {
		b = false
	} else {
		b = true
	}
	return b
}
