package proto

import (
	"fmt"
	"math"
	"reflect"
)

func GetMax[T float64 | float32 | int | uint | int8 | uint8 | int16 | uint16 | int32 | uint32 | int64 | uint64](a, b T) T {
  if a > b {
    return a
  }
  return b
}

func GetMin[T float64 | float32 | int | uint | int8 | uint8 | int16 | uint16 | int32 | uint32 | int64 | uint64](a, b T) T {
  if a < b {
    return a
  }
  return b
}


type EmployeeField string

const (
	FieldEmpID                 EmployeeField = "EmpId"
	FieldNamePrefix                          = "NamePrefix"
	FieldFirstName                           = "FirstName"
	FieldMiddleInitial                       = "MiddleInitial"
	FieldLastName                            = "LastName"
	FieldGender                              = "Gender"
	FieldEmail                               = "Email"
	FieldFathersName                         = "FathersName"
	FieldMothersName                         = "MothersName"
	FieldMothersMaidenName                   = "MothersMaidenName"
	FieldBirthdate                           = "Birthdate"
	FieldBirthTime                           = "BirthTime"
	FieldWeightKg                            = "WeightKg"
	FieldJoiningDate                         = "JoiningDate"
	FieldJoiningQuarter                      = "JoiningQuarter"
	FieldJoiningHalf                         = "JoiningHalf"
	FieldJoiningYear                         = "JoiningYear"
	FieldJoiningMonth                        = "JoiningMonth"
	FieldJoiningMonthName                    = "JoiningMonthName"
	FieldJoiningMonthNameShort               = "JoiningMonthNameShort"
	FieldJoiningMonthDay                     = "JoiningMonthDay"
	FieldJoiningWeekDay                      = "JoiningWeekDay"
	FieldJoiningWeekDayShort                 = "JoiningWeekDayShort"
	FieldYearsOfService                      = "YearsOfService"
	FieldSalary                              = "Salary"
	FieldLatestHikePercentage                = "LatestHikePercentage"
	FieldSsn                                 = "Ssn"
	FieldPhoneNumber                         = "PhoneNumber"
	FieldPlaceName                           = "PlaceName"
	FieldCounty                              = "County"
	FieldCity                                = "City"
	FieldState                               = "State"
	FieldZip                                 = "Zip"
	FieldRegion                              = "Region"
	FieldUsername                            = "Username"
	FieldPassword                            = "Password"
)

func (e *Employee) Copy() Employee {
	return Employee{
		EmpId:                 e.GetEmpId(),
		NamePrefix:            e.GetNamePrefix(),
		FirstName:             e.GetFirstName(),
		MiddleInitial:         e.GetMiddleInitial(),
		LastName:              e.GetLastName(),
		Gender:                e.GetGender(),
		Email:                 e.GetEmail(),
		FathersName:           e.GetFathersName(),
		MothersName:           e.GetMothersName(),
		MothersMaidenName:     e.GetMothersMaidenName(),
		Birthdate:             e.GetBirthdate(),
		BirthTime:             e.GetBirthTime(),
		WeightKg:              e.GetWeightKg(),
		JoiningDate:           e.GetJoiningDate(),
		JoiningQuarter:        e.GetJoiningQuarter(),
		JoiningHalf:           e.GetJoiningHalf(),
		JoiningYear:           e.GetJoiningYear(),
		JoiningMonth:          e.GetJoiningMonth(),
		JoiningMonthName:      e.GetJoiningMonthName(),
		JoiningMonthNameShort: e.GetJoiningMonthNameShort(),
		JoiningMonthDay:       e.GetJoiningMonthDay(),
		JoiningWeekDay:        e.GetJoiningWeekDay(),
		JoiningWeekDayShort:   e.GetJoiningWeekDayShort(),
		YearsOfService:        e.GetYearsOfService(),
		Salary:                e.GetSalary(),
		LatestHikePercentage:  e.GetLatestHikePercentage(),
		Ssn:                   e.GetSsn(),
		PhoneNumber:           e.GetPhoneNumber(),
		PlaceName:             e.GetPlaceName(),
		County:                e.GetCounty(),
		City:                  e.GetCity(),
		State:                 e.GetState(),
		Zip:                   e.GetZip(),
		Region:                e.GetRegion(),
		Username:              e.GetUsername(),
		Password:              e.GetPassword(),
	}
}

func (el *EmployeeList) QueryEmployeesByColumn(column EmployeeField, query string) ([]*Employee, error) {
	employees := make([]*Employee, 0)

	for _, employee := range el.GetEmployees() {
		value := reflect.ValueOf(employee).Elem().FieldByName(string(column))
		if fmt.Sprintf("%v", value) == query {
			employees = append(employees, employee)
		}
	}

	return employees, nil
}

type Groups map[string][]*Employee

func (el *EmployeeList) GroupByColumn(column EmployeeField) Groups {

	groups := make(map[string][]*Employee)
	for _, employee := range el.GetEmployees() {
		value := reflect.ValueOf(employee).Elem().FieldByName(string(column))
		formattedValue := fmt.Sprintf("%v", value)
		groups[formattedValue] = append(groups[formattedValue], employee)
	}

	return groups
}

type FilterFunction func(*Employee) bool

func (grps Groups) HavingByColumn(cb FilterFunction) Groups {

	newGrps := make(map[string][]*Employee)

	for groupName, group := range grps {
		for _, employee := range group {
			if cb(employee) {
				newGrps[groupName] = append(newGrps[groupName], employee)
			}
		}
	}

	return newGrps
}

func ForEachGroup[T any](grps Groups, cb func(ep EmployeePList) T) map[string]T {
  g := make(map[string]T)
  for groupName, group := range grps {
    g[groupName] = cb(group)
  }

  return g
} 

func AggregateByColumn[T any](el *EmployeeList, column EmployeeField, cb func(ep EmployeePList) T) map[string]T {

  grps := el.GroupByColumn(column)
  res := ForEachGroup(grps, cb) 

  return res
}

type EmployeePList []*Employee

func Sum(ep EmployeePList, column EmployeeField) float64 {
	sum := 0.0

	_, fieldFound := reflect.TypeOf(Employee{}).FieldByName(string(column))
	if !fieldFound {
		return sum
	}

	for _, employee := range ep {
    value := reflect.ValueOf(employee).Elem().FieldByName(string(column))
    switch value.Kind() {
    case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
      sum += float64(value.Int())
    case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
      sum += float64(value.Uint())
    case reflect.Float64, reflect.Float32:
      sum += value.Float()
    default:
      return sum
    }
	}
  return sum 
}

func Average(ep EmployeePList, column EmployeeField) float64 {
	return Sum(ep, column) / float64(len(ep))
}

func Max(ep EmployeePList, column EmployeeField) float64 {
  max := 0.0
  
	_, fieldFound := reflect.TypeOf(Employee{}).FieldByName(string(column))
	if !fieldFound {
		return max
	}

	for _, employee := range ep {
    value := reflect.ValueOf(employee).Elem().FieldByName(string(column))
    switch value.Kind() {
    case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
      max = GetMax(max, float64(value.Int()))
    case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
      max = GetMax(max, float64(value.Uint()))
    case reflect.Float64, reflect.Float32:
      max = GetMax(max, value.Float())
    default:
      return max
    }
	}

  return max
}

func Min(ep EmployeePList, column EmployeeField) float64 {
  min := math.MaxFloat64
  
  _, fieldFound := reflect.TypeOf(Employee{}).FieldByName(string(column))
  if !fieldFound {
    return min
  }

	for _, employee := range ep {
    value := reflect.ValueOf(employee).Elem().FieldByName(string(column))
    switch value.Kind() {
    case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
      min = GetMin(min, float64(value.Int()))
    case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
      min = GetMin(min, float64(value.Uint()))
    case reflect.Float64, reflect.Float32:
      min = GetMin(min, value.Float())
    default:
      return min
    }
	}

  return min
}
