package sync

import (
	"context"
	"fmt"
	"strings"
	"time"

	"github.com/Gleipnir-Technology/bob"
	"github.com/Gleipnir-Technology/bob/dialect/psql"
	"github.com/Gleipnir-Technology/bob/dialect/psql/sm"
	"github.com/Gleipnir-Technology/nidus-sync/db"
	"github.com/Gleipnir-Technology/nidus-sync/db/models"
	"github.com/Gleipnir-Technology/nidus-sync/db/sql"
	"github.com/Gleipnir-Technology/nidus-sync/notification"
	"github.com/google/uuid"
	"github.com/uber/h3-go/v4"
)

func breedingSourcesByCell(ctx context.Context, org *models.Organization, c h3.Cell) ([]BreedingSourceSummary, error) {
	var results []BreedingSourceSummary

	boundary, err := c.Boundary()
	if err != nil {
		return results, fmt.Errorf("Failed to get cell boundary: %w", err)
	}
	geom_query := gisStatement(boundary)
	rows, err := org.Pointlocations(
		sm.Where(
			psql.F("ST_Within", "geospatial", geom_query),
		),
		sm.OrderBy("lasttreatdate"),
	).All(ctx, db.PGInstance.BobDB)
	if err != nil {
		return results, fmt.Errorf("Failed to query rows: %w", err)
	}
	for _, r := range rows {
		var last_inspected *time.Time
		if !r.Lastinspectdate.IsNull() {
			l := r.Lastinspectdate.MustGet()
			last_inspected = &l
		}
		var last_treat_date *time.Time
		if !r.Lasttreatdate.IsNull() {
			l := r.Lasttreatdate.MustGet()
			last_treat_date = &l
		}
		results = append(results, BreedingSourceSummary{
			ID:            r.Globalid,
			LastInspected: last_inspected,
			LastTreated:   last_treat_date,
			Type:          r.Habitat.GetOr("none"),
		})
	}
	return results, nil
}
func gisStatement(cb h3.CellBoundary) string {
	var content strings.Builder
	for i, p := range cb {
		if i != 0 {
			content.WriteString(", ")
		}
		content.WriteString(fmt.Sprintf("%f %f", p.Lng, p.Lat))
	}
	// Repeat the first coordinate to close the polygon
	content.WriteString(fmt.Sprintf(", %f %f", cb[0].Lng, cb[0].Lat))
	return fmt.Sprintf("ST_GeomFromText('POLYGON((%s))', 3857)", content.String())
}

func sourceByGlobalId(ctx context.Context, org *models.Organization, id uuid.UUID) (*BreedingSourceDetail, error) {
	row, err := org.Pointlocations(
		sm.Where(models.FieldseekerPointlocations.Columns.Globalid.EQ(psql.Arg(id))),
	).One(ctx, db.PGInstance.BobDB)
	if err != nil {
		return nil, fmt.Errorf("Failed to get point location: %w", err)
	}
	return toTemplateBreedingSource(row), nil
}

func extractInitials(name string) string {
	parts := strings.Fields(name)
	var initials strings.Builder

	for _, part := range parts {
		if len(part) > 0 {
			initials.WriteString(strings.ToUpper(string(part[0])))
		}
	}

	return initials.String()
}

func contentForUser(ctx context.Context, user *models.User) (User, error) {
	notifications, err := notification.ForUser(ctx, user)
	if err != nil {
		return User{}, err
	}
	org := user.R.Organization
	var organization Organization
	if org != nil {
		organization.ID = int(org.ID)
		organization.Name = org.Name
	}
	return User{
		DisplayName:   user.DisplayName,
		Initials:      extractInitials(user.DisplayName),
		Notifications: notifications,
		Organization:  organization,
		Role:          user.Role.String(),
		Username:      user.Username,
	}, nil

}

func trapsBySource(ctx context.Context, org *models.Organization, sourceID uuid.UUID) ([]TrapNearby, error) {
	locations, err := sql.TrapLocationBySourceID(org.ID, sourceID).All(ctx, db.PGInstance.BobDB)
	if err != nil {
		return nil, fmt.Errorf("Failed to query rows: %w", err)
	}

	location_ids := make([]uuid.UUID, 0)
	var args []bob.Expression
	for _, location := range locations {
		location_ids = append(location_ids, location.TrapLocationGlobalid)
		args = append(args, psql.Arg(location.TrapLocationGlobalid))
	}
	/*
		trap_data, err := org.FSTrapdata(
			sm.Where(
				models.FSTrapdata.Columns.LocID.In(args...),
			),
			sm.OrderBy("enddatetime"),
		).All(ctx, db.PGInstance.BobDB)
	*/

	/*
		query := org.FSTrapdata(
			sm.From(
				psql.Select(
					sm.From(psql.F("ROW_NUMBER")(
						fm.Over(
							wm.PartitionBy(models.FSTrapdata.Columns.LocID),
							wm.OrderBy(models.FSTrapdata.Columns.Enddatetime).Desc(),
						),
					)).As("row_num"),
				sm.Where(models.FSTrapdata.Columns.LocID.In(args...))),
			),
			sm.Where(psql.Quote("row_num").LTE(psql.Arg(10))),
			sm.OrderBy(models.FSTrapdata.Columns.LocID),
			sm.OrderBy(models.FSTrapdata.Columns.Enddatetime).Desc(),
		)
	*/
	/*
		query := psql.Select(
			sm.From(
				psql.Select(
					sm.Columns(
						models.FSTrapdata.Columns.Globalid,
						psql.F("ROW_NUMBER")(
						fm.Over(
							wm.PartitionBy(models.FSTrapdata.Columns.LocID),
							wm.OrderBy(models.FSTrapdata.Columns.Enddatetime).Desc(),
						),
					).As("row_num"),
					sm.From(models.FSTrapdata.Name()),
				),
				sm.Where(models.FSTrapdata.Columns.LocID.In(args...))),
			),
			sm.Where(psql.Quote("row_num").LTE(psql.Arg(10))),
			sm.OrderBy(models.FSTrapdata.Columns.LocID),
			sm.OrderBy(models.FSTrapdata.Columns.Enddatetime).Desc(),
		)
		log.Info().Str("trapdata", queryToString(query)).Msg("Getting trap data")
		trap_data, err := query.Exec(ctx, db.PGInstance.BobDB)
	*/

	trap_data, err := sql.TrapDataByLocationIDRecent(org.ID, location_ids).All(ctx, db.PGInstance.BobDB)
	if err != nil {
		return nil, fmt.Errorf("Failed to query trap data: %w", err)
	}

	counts, err := sql.TrapCountByLocationID(org.ID, location_ids).All(ctx, db.PGInstance.BobDB)
	if err != nil {
		return nil, fmt.Errorf("Failed to query trap counts: %w", err)
	}

	traps, err := toTemplateTrapsNearby(locations, trap_data, counts)
	if err != nil {
		return nil, fmt.Errorf("Failed to convert trap data: %w", err)
	}
	return traps, nil
}

func treatmentsBySource(ctx context.Context, org *models.Organization, sourceID uuid.UUID) ([]Treatment, error) {
	var results []Treatment
	rows, err := org.Treatments(
		sm.Where(
			models.FieldseekerTreatments.Columns.Pointlocid.EQ(psql.Arg(sourceID)),
		),
		sm.OrderBy("enddatetime").Desc(),
	).All(ctx, db.PGInstance.BobDB)
	if err != nil {
		return results, fmt.Errorf("Failed to query rows: %w", err)
	}
	//log.Info().Int("row count", len(rows)).Msg("Getting treatments")
	return toTemplateTreatment(rows)
}

func trapByGlobalId(ctx context.Context, org *models.Organization, id uuid.UUID) (result Trap, err error) {
	row, err := org.Traplocations(
		sm.Where(models.FieldseekerTraplocations.Columns.Globalid.EQ(psql.Arg(id))),
	).One(ctx, db.PGInstance.BobDB)
	if err != nil {
		return result, fmt.Errorf("Failed to get trap location: %w", err)
	}

	trap_data, err := sql.TrapDataByLocationIDRecent(org.ID, []uuid.UUID{id}).All(ctx, db.PGInstance.BobDB)
	if err != nil {
		return result, fmt.Errorf("Failed to query trap data: %w", err)
	}

	counts, err := sql.TrapCountByLocationID(org.ID, []uuid.UUID{id}).All(ctx, db.PGInstance.BobDB)
	if err != nil {
		return result, fmt.Errorf("Failed to query trap counts: %w", err)
	}

	return toTemplateTrap(row, trap_data, counts)
}

func trapsByCell(ctx context.Context, org *models.Organization, c h3.Cell) (results []TrapSummary, err error) {
	boundary, err := c.Boundary()
	if err != nil {
		return results, fmt.Errorf("Failed to get cell boundary: %w", err)
	}
	geom_query := gisStatement(boundary)
	rows, err := org.Traplocations(
		sm.Where(
			psql.F("ST_Within", "geospatial", geom_query),
		),
		sm.OrderBy("objectid"),
	).All(ctx, db.PGInstance.BobDB)
	if err != nil {
		return results, fmt.Errorf("Failed to query rows: %w", err)
	}
	return toTemplateTrapSummary(rows)
}

func treatmentsByCell(ctx context.Context, org *models.Organization, c h3.Cell) ([]Treatment, error) {
	var results []Treatment
	boundary, err := c.Boundary()
	if err != nil {
		return results, fmt.Errorf("Failed to get cell boundary: %w", err)
	}
	geom_query := gisStatement(boundary)
	rows, err := org.Treatments(
		sm.Where(
			psql.F("ST_Within", "geospatial", geom_query),
		),
		sm.OrderBy("pointlocid"),
		sm.OrderBy("enddatetime"),
	).All(ctx, db.PGInstance.BobDB)
	if err != nil {
		return results, fmt.Errorf("Failed to query rows: %w", err)
	}
	return toTemplateTreatment(rows)
}
func inspectionsByCell(ctx context.Context, org *models.Organization, c h3.Cell) ([]Inspection, error) {
	var results []Inspection

	boundary, err := c.Boundary()
	if err != nil {
		return results, fmt.Errorf("Failed to get cell boundary: %w", err)
	}
	geom_query := gisStatement(boundary)
	rows, err := org.Mosquitoinspections(
		sm.Where(
			psql.F("ST_Within", "geospatial", geom_query),
		),
		sm.OrderBy("pointlocid"),
		sm.OrderBy("enddatetime"),
	).All(ctx, db.PGInstance.BobDB)
	if err != nil {
		return results, fmt.Errorf("Failed to query rows: %w", err)
	}
	return toTemplateInspection(rows)
}
func inspectionsBySource(ctx context.Context, org *models.Organization, sourceID uuid.UUID) ([]Inspection, error) {
	var results []Inspection

	rows, err := org.Mosquitoinspections(
		sm.Where(
			models.FieldseekerMosquitoinspections.Columns.Pointlocid.EQ(psql.Arg(sourceID)),
		),
		sm.OrderBy("enddatetime").Desc(),
	).All(ctx, db.PGInstance.BobDB)
	if err != nil {
		return results, fmt.Errorf("Failed to query rows: %w", err)
	}
	return toTemplateInspection(rows)
}
