package salestracking

import (
	"encoding/json"
	"time"

	"github.com/jackc/pgx/v5/pgtype"
	"github.com/shopspring/decimal"

	"github.com/TaruLab/keluargamart-backend/internal/db"
)

// The sqlc row structs for visits are identical across ListSalesVisits and
// GetSalesVisitByID, so one converter per entity suffices; callers convert
// their row type explicitly: visitFromRow(db.GetSalesVisitByIDRow(row)).

func visitFromRow(r db.GetSalesVisitByIDRow) *SalesVisit {
	v := &SalesVisit{
		ID:           r.ID,
		SalesID:      int8To64(r.SalesID),
		CustomerID:   int8To64(r.CustomerID),
		VisitDate:    dateToStr(r.VisitDate),
		CheckInTime:  tsToPtr(r.CheckInTime),
		CheckOutTime: tsToPtr(r.CheckOutTime),
		CheckInLat:   ndToFloatPtr(r.CheckInLat),
		CheckInLng:   ndToFloatPtr(r.CheckInLng),
		CheckOutLat:  ndToFloatPtr(r.CheckOutLat),
		CheckOutLng:  ndToFloatPtr(r.CheckOutLng),
		Purpose:      textOr(r.Purpose),
		Result:       textOr(r.Result),
		Notes:        textOr(r.Notes),
		SalesOrderID: int8ToPtr(r.SalesOrderID),
		Status:       r.Status,
		VisitType:    r.VisitType,
		ScheduledBy:  int8ToPtr(r.ScheduledBy),
	}
	if r.ID_2.Valid {
		v.Customer = &CustomerRef{
			ID:           r.ID_2.Int64,
			Code:         textOr(r.Code),
			Name:         textOr(r.Name),
			CustomerType: textOr(r.CustomerType),
			CreditLimit:  ndOrZero(r.CreditLimit),
		}
	}
	return v
}

func visitFromListRow(r db.ListSalesVisitsRow) *SalesVisit {
	v := visitFromRow(db.GetSalesVisitByIDRow{
		ID: r.ID, SalesID: r.SalesID, CustomerID: r.CustomerID,
		ID_2: r.ID_2, Code: r.Code, Name: r.Name, CustomerType: r.CustomerType, CreditLimit: r.CreditLimit,
		VisitDate: r.VisitDate, CheckInTime: r.CheckInTime, CheckOutTime: r.CheckOutTime,
		CheckInLat: r.CheckInLat, CheckInLng: r.CheckInLng, CheckOutLat: r.CheckOutLat, CheckOutLng: r.CheckOutLng,
		Purpose: r.Purpose, Result: r.Result, Notes: r.Notes, SalesOrderID: r.SalesOrderID, Status: r.Status,
		VisitType: r.VisitType, ScheduledBy: r.ScheduledBy,
	})
	v.SalesName = r.SalesName
	return v
}

func activityFromRow(r db.ListSalesActivitiesRow) *SalesActivity {
	return &SalesActivity{
		ID:            r.ID,
		SalesID:       int8To64(r.SalesID),
		SalesVisitID:  int8ToPtr(r.SalesVisitID),
		ActivityDate:  dateToStr(r.ActivityDate),
		ActivityType:  r.ActivityType,
		Description:   textOr(r.Description),
		Result:        textOr(r.Result),
		FollowUpDate:  dateToPtr(r.FollowUpDate),
		FollowUpNotes: textOr(r.FollowUpNotes),
		SalesName:     r.SalesName,
		CustomerName:  r.CustomerName,
		Time:          r.Time,
		Notes:         r.Notes,
	}
}

func dateToPtr(v pgtype.Date) *string {
	if !v.Valid {
		return nil
	}
	s := v.Time.Format("2006-01-02")
	return &s
}

func routeFromRow(r db.ListSalesRoutesRow) *SalesRoute {
	rt := &SalesRoute{
		ID:               r.ID,
		SalesID:          int8To64(r.SalesID),
		RouteDate:        dateToStr(r.RouteDate),
		Name:             textOr(r.Name),
		PlannedCustomers: PlannedCustomers{},
		Status:           r.Status,
		Notes:            textOr(r.Notes),
		SalesName:        r.SalesName,
		LocationCount:    r.LocationCount,
	}
	if len(r.PlannedCustomers) > 0 {
		var pc PlannedCustomers
		if err := json.Unmarshal(r.PlannedCustomers, &pc); err == nil {
			rt.PlannedCustomers = pc
		}
	}
	return rt
}

func locationFromRow(r db.LatestSalesLocationsRow) SalesLocation {
	return SalesLocation{
		ID:           r.ID,
		SalesID:      int8To64(r.SalesID),
		Latitude:     ndToFloat(r.Latitude),
		Longitude:    ndToFloat(r.Longitude),
		Accuracy:     ndToFloatPtr(r.Accuracy),
		Speed:        ndToFloatPtr(r.Speed),
		Heading:      ndToFloatPtr(r.Heading),
		BatteryLevel: i2ToPtr(r.BatteryLevel),
		RecordedAt:   tsToPtr(r.RecordedAt),
	}
}

// --- pgtype helpers ----------------------------------------------------------

func int8To64(v pgtype.Int8) int64 {
	if !v.Valid {
		return 0
	}
	return v.Int64
}

func int8ToPtr(v pgtype.Int8) *int64 {
	if !v.Valid {
		return nil
	}
	x := v.Int64
	return &x
}

func int8(v int64) pgtype.Int8 {
	return pgtype.Int8{Int64: v, Valid: true}
}

func int8OrNil(v *int64) pgtype.Int8 {
	if v == nil {
		return pgtype.Int8{}
	}
	return pgtype.Int8{Int64: *v, Valid: true}
}

func textOr(v pgtype.Text) string {
	if !v.Valid {
		return ""
	}
	return v.String
}

func textOrNil(v string) pgtype.Text {
	if v == "" {
		return pgtype.Text{}
	}
	return pgtype.Text{String: v, Valid: true}
}

func dateToStr(v pgtype.Date) string {
	if !v.Valid {
		return ""
	}
	return v.Time.Format("2006-01-02")
}

func dateOrNil(v string) pgtype.Date {
	if v == "" {
		return pgtype.Date{}
	}
	if t, err := time.Parse("2006-01-02", v); err == nil {
		return pgtype.Date{Time: t, Valid: true}
	}
	return pgtype.Date{}
}

func tsToPtr(v pgtype.Timestamptz) *time.Time {
	if !v.Valid {
		return nil
	}
	t := v.Time
	return &t
}

func tsToPgtz(v *time.Time) pgtype.Timestamptz {
	if v == nil {
		return pgtype.Timestamptz{}
	}
	return pgtype.Timestamptz{Time: *v, Valid: true}
}

func ndToFloat(v decimal.NullDecimal) float64 {
	if !v.Valid {
		return 0
	}
	f, _ := v.Decimal.Float64()
	return f
}

func ndToFloatPtr(v decimal.NullDecimal) *float64 {
	if !v.Valid {
		return nil
	}
	f, _ := v.Decimal.Float64()
	return &f
}

func ndOrZero(v decimal.NullDecimal) decimal.Decimal {
	if !v.Valid {
		return decimal.Zero
	}
	return v.Decimal
}

func floatToNd(v *float64) decimal.NullDecimal {
	if v == nil {
		return decimal.NullDecimal{}
	}
	return decimal.NullDecimal{Decimal: decimal.NewFromFloat(*v), Valid: true}
}

func i2ToPtr(v pgtype.Int2) *int {
	if !v.Valid {
		return nil
	}
	x := int(v.Int16)
	return &x
}

func i2FromInt(v *int) pgtype.Int2 {
	if v == nil {
		return pgtype.Int2{}
	}
	return pgtype.Int2{Int16: int16(*v), Valid: true}
}
