package app

import (
	"database/sql"
	"fmt"
	"reflect"
	"time"
)

const dateFormat = "2006-01-02"
const dateTimeFormat = "2006-01-02T15:04:05-0800"

// String takes a string and returns a pointer to the string.
func String(s string) *string {
	return &s
}

// Bool takes a bool and returns a pointer to the bool.
func Bool(b bool) *bool {
	return &b
}

// Int64 takes an int64 and returns a pointer to the int64.
func Int64(i int64) *int64 {
	return &i
}

// Int takes an int and returns a pointer to the int.
func Int(i int) *int {
	return &i
}

// Time takes a time.Time and returns a pointer to the time.Time.
func Time(t time.Time) *time.Time {
	return &t
}

// Float64 takes a float64 and returns a pointer to the float64.
func Float64(f float64) *float64 {
	return &f
}

// FieldsAndValues is a struct that holds field names and corresponding values for an object.
type FieldsAndValues struct {
	fieldNames  []string
	fieldValues []interface{}
}

// GetFieldsAndValues takes an object and returns a pointer to a FieldsAndValues struct
// containing the field names and corresponding values for each non-zero field,
// excluding the "ID" field.
func GetFieldsAndValues(obj interface{}) *FieldsAndValues {
	result := &FieldsAndValues{
		fieldNames:  []string{},
		fieldValues: []interface{}{},
	}

	v := reflect.ValueOf(obj).Elem()
	t := v.Type()

	for i := 0; i < t.NumField(); i++ {
		field := t.Field(i)
		fieldValue := v.Field(i)

		if field.Name != "ID" {
			if !IsZero(fieldValue) {
				fmt.Printf("DEBUG: Adding field %s with value %v\n", field.Name, fieldValue.Interface())
				result.fieldNames = append(result.fieldNames, field.Name)
				result.fieldValues = append(result.fieldValues, fieldValue.Interface())
			}
		}
	}

	return result
}

// IsZero checks if a value is zero or considered zero-like (e.g., empty arrays or slices, nil pointers).
// Note that strings, including empty strings, are treated as non-zero values.
func IsZero(v reflect.Value) bool {
	switch v.Kind() {
	case reflect.String:
		return false // Treat strings, including empty strings, as non-zero values
	case reflect.Struct:
		for i := 0; i < v.NumField(); i++ {
			if !IsZero(v.Field(i)) {
				return false
			}
		}
		return true
	case reflect.Ptr, reflect.Interface:
		if v.IsNil() {
			return true
		}
		return IsZero(v.Elem())
	case reflect.Array, reflect.Slice:
		return v.Len() == 0
	default:
		z := reflect.Zero(v.Type())
		return v.Interface() == z.Interface()
	}
}

// SqlDateToString takes a pointer to a sql.NullTime object and returns a pointer to a string
// representing the date in dateTimeFormat if the sql.NullTime object is valid, otherwise returns nil.
func SqlDateToString(d *sql.NullTime) *string {
	if d == nil || !d.Valid {
		return nil
	}

	dateStr := d.Time.Format(dateTimeFormat)
	return &dateStr
}

// StringToSqlDate takes a pointer to a string representing a date and returns a pointer to
// a sql.NullTime object containing the parsed date if the string is not nil, otherwise returns nil.
func StringToSqlDate(s *string) *sql.NullTime {
	if s == nil {
		return nil
	}

	parsedTime, err := ParseDateTime(s)
	if err != nil {
		return nil
	}

	return &sql.NullTime{
		Time:  *parsedTime,
		Valid: true,
	}
}

// ParseDateTime takes a pointer to a string representing a date and returns a pointer to
// a time.Time object containing the parsed date if the string is in one of the supported formats.
// If the string is nil or not in a supported format, returns an error.
func ParseDateTime(dateStr *string) (*time.Time, error) {
	if dateStr == nil {
		return nil, fmt.Errorf("✋members.parseDateTime: dateStr is null")
	}
	loc, _ := time.LoadLocation("Local")
	//
	formats := []string{dateFormat, dateTimeFormat, time.RFC1123, time.RFC3339}

	var parsedTime *time.Time
	for _, format := range formats {
		tm, err := time.ParseInLocation(format, *dateStr, loc)
		if err == nil {
			parsedTime = &tm
			break
		}
	}

	if parsedTime == nil {
		return nil, fmt.Errorf("✋parseDateTime: input string doesn't match any of the expected formats")
	}

	return parsedTime, nil
}
