package usecasehelper

import (
	"errors"
	"fmt"
	"net/url"
	"strings"
	"time"

	pl "simrs-vx/pkg/logger"

	"gorm.io/gorm"
)

func SafeToResponse[T any](data *T) any {
	if data == nil {
		return nil
	}

	// Use type assertion to call ToResponse if the type has it
	if converter, ok := any(data).(interface{ ToResponse() any }); ok {
		return converter.ToResponse()
	}

	if converter, ok := any(*data).(interface{ ToResponse() any }); ok {
		return converter.ToResponse()
	}

	return nil
}

func HandleReadError(err error, event *pl.Event, itemType string, id interface{}, data any) error {
	if err == nil {
		pl.SetLogInfo(event, data, "complete")
		return nil
	}

	if errors.Is(err, gorm.ErrRecordNotFound) {
		event.Status = "failed"
		event.ErrInfo = pl.ErrorInfo{
			Code:   "data-notFound",
			Detail: fmt.Sprintf("%s with ID %v not found", itemType, id),
			Raw:    err,
		}
	} else {
		event.Status = "failed"
		event.ErrInfo = pl.ErrorInfo{
			Code:   "data-read-detail-fail",
			Detail: fmt.Sprintf("%s read failed", itemType),
			Raw:    err,
		}
	}

	return pl.SetLogError(event, nil)
}

func HandleSearchError(err error, event *pl.Event, itemType string, query interface{}, data any) error {
	if err == nil {
		pl.SetLogInfo(event, data, "complete")
		return nil
	}

	if errors.Is(err, gorm.ErrRecordNotFound) {
		event.Status = "failed"
		event.ErrInfo = pl.ErrorInfo{
			Code:   "data-notFound",
			Detail: fmt.Sprintf("%s with query '%v' not found", itemType, query),
			Raw:    err,
		}
	} else {
		event.Status = "failed"
		event.ErrInfo = pl.ErrorInfo{
			Code:   "data-search-fail",
			Detail: fmt.Sprintf("%s search failed", itemType),
			Raw:    err,
		}
	}

	return pl.SetLogError(event, nil)
}

func GetMiddlewareErrorCode(mwType MWType) string {
	if strings.Contains(string(mwType), "Pre") {
		return "MW_PRE_FAILED"
	}
	return "MW_POST_FAILED"
}

// GetLogData returns whichever of data or input is non-nil (prefers data)
func GetLogData(input interface{}, data interface{}) interface{} {
	if data != nil {
		return data
	}
	return input
}

func HandleMiddlewareError(event *pl.Event, mwType, mwName string, logData interface{}, err error) error {
	event.Status = "failed"
	event.ErrInfo = pl.ErrorInfo{
		Code:   GetMiddlewareErrorCode(MWType(mwType)),
		Detail: fmt.Sprintf("%s middleware %s failed: %s", mwType, mwName, err.Error()),
		Raw:    err,
	}
	return pl.SetLogError(event, logData)
}

func IsDataNotFoundError(err error) bool {
	if err == nil {
		return false
	}
	return strings.Contains(err.Error(), "code: data-notFound")
}

func AddPrefix(prefix string, str string) string {
	return prefix + str
}

func GetPreloads(input string) []string {
	result := []string{}
	parts := strings.Split(input, ",")
	for _, p := range parts {
		subParts := strings.Split(p, "-")
		for i := range subParts {
			subParts[i] = kebabToPascal(subParts[i])
		}
		result = append(result, strings.Join(subParts, "."))
	}
	return result
}

func kebabToPascal(input string) string {
	parts := strings.Split(input, "-")
	for i, p := range parts {
		if len(p) > 0 {
			r := []rune(p)
			r[0] = []rune(strings.ToUpper(string(r[0])))[0]
			parts[i] = string(r)
		}
	}
	return strings.Join(parts, "")
}

func GetTimeNow() *time.Time {
	tmp := time.Now()
	return &tmp
}

func IsDateBeforeNow(t *time.Time) bool {
	if t == nil {
		return false
	}
	return t.Before(time.Now())
}

// Contains reports whether v is present in s.
func Contains[S ~[]E, E comparable](s S, v E) bool {
	return index(s, v) >= 0
}

// Index returns the index of the first occurrence of v in s,
// or -1 if not present.
func index[S ~[]E, E comparable](s S, v E) int {
	for i := range s {
		if v == s[i] {
			return i
		}
	}
	return -1
}

func FormatIndonesianDate(t time.Time) string {
	monthNames := [...]string{
		"", // dummy index 0
		"Januari",
		"Februari",
		"Maret",
		"April",
		"Mei",
		"Juni",
		"Juli",
		"Agustus",
		"September",
		"Oktober",
		"November",
		"Desember",
	}

	return fmt.Sprintf("%d %s %d", t.Day(), monthNames[int(t.Month())], t.Year())
}

func GetLastTwoPathSegments(s string) string {
	u, err := url.Parse(s)
	var path string

	if err == nil && u.Path != "" {
		path = u.Path
	} else {
		path = s
	}

	parts := strings.Split(strings.Trim(path, "/"), "/")
	n := len(parts)

	if n >= 2 {
		return parts[n-2] + "/" + parts[n-1]
	}

	// fallback: return entire string if less than 2 segments
	return strings.Trim(path, "/")
}

func FormatPlaceAndDate(place string, t time.Time) string {
	// Ensure place is uppercase
	place = strings.ToUpper(strings.TrimSpace(place))

	// Format date: DD-MM-YYYY
	dateStr := t.Format("02-01-2006")

	return place + ", " + dateStr
}
