package service

import (
	"crypto/rand"
	"encoding/json"
	"fmt"
	"math/big"
	"net/http"
	"strings"

	"cmr-backend/internal/apperr"
)

const (
	AssignmentModeManual         = "manual"
	AssignmentModeRandom         = "random"
	AssignmentModeServerAssigned = "server-assigned"
)

type CourseVariantView struct {
	ID          string  `json:"id"`
	Name        string  `json:"name"`
	Description *string `json:"description,omitempty"`
	RouteCode   *string `json:"routeCode,omitempty"`
	Selectable  bool    `json:"selectable"`
}

type VariantBindingView struct {
	ID             string  `json:"id"`
	Name           string  `json:"name"`
	RouteCode      *string `json:"routeCode,omitempty"`
	AssignmentMode string  `json:"assignmentMode"`
}

type VariantPlan struct {
	AssignmentMode *string
	CourseVariants []CourseVariantView
}

func resolveVariantPlan(payloadJSON *string) VariantPlan {
	if payloadJSON == nil || strings.TrimSpace(*payloadJSON) == "" {
		return VariantPlan{}
	}

	var payload map[string]any
	if err := json.Unmarshal([]byte(*payloadJSON), &payload); err != nil {
		return VariantPlan{}
	}

	play, _ := payload["play"].(map[string]any)
	if len(play) == 0 {
		return VariantPlan{}
	}

	result := VariantPlan{}
	if rawMode, ok := play["assignmentMode"].(string); ok {
		if normalized := normalizeAssignmentMode(rawMode); normalized != nil {
			result.AssignmentMode = normalized
		}
	}

	rawVariants, _ := play["courseVariants"].([]any)
	if len(rawVariants) == 0 {
		return result
	}

	for _, raw := range rawVariants {
		item, ok := raw.(map[string]any)
		if !ok {
			continue
		}
		id, _ := item["id"].(string)
		name, _ := item["name"].(string)
		id = strings.TrimSpace(id)
		name = strings.TrimSpace(name)
		if id == "" || name == "" {
			continue
		}
		var description *string
		if value, ok := item["description"].(string); ok && strings.TrimSpace(value) != "" {
			trimmed := strings.TrimSpace(value)
			description = &trimmed
		}
		var routeCode *string
		if value, ok := item["routeCode"].(string); ok && strings.TrimSpace(value) != "" {
			trimmed := strings.TrimSpace(value)
			routeCode = &trimmed
		}
		selectable := true
		if value, ok := item["selectable"].(bool); ok {
			selectable = value
		}
		result.CourseVariants = append(result.CourseVariants, CourseVariantView{
			ID:          id,
			Name:        name,
			Description: description,
			RouteCode:   routeCode,
			Selectable:  selectable,
		})
	}

	return result
}

func resolveLaunchVariant(plan VariantPlan, requestedVariantID string) (*VariantBindingView, error) {
	requestedVariantID = strings.TrimSpace(requestedVariantID)
	if len(plan.CourseVariants) == 0 {
		return nil, nil
	}

	mode := AssignmentModeManual
	if plan.AssignmentMode != nil {
		mode = *plan.AssignmentMode
	}

	if requestedVariantID != "" {
		for _, item := range plan.CourseVariants {
			if item.ID == requestedVariantID {
				if !item.Selectable && mode == AssignmentModeManual {
					return nil, apperr.New(http.StatusBadRequest, "variant_not_selectable", "requested variant is not selectable")
				}
				return &VariantBindingView{
					ID:             item.ID,
					Name:           item.Name,
					RouteCode:      item.RouteCode,
					AssignmentMode: mode,
				}, nil
			}
		}
		return nil, apperr.New(http.StatusBadRequest, "variant_not_found", "requested variant does not exist")
	}

	selected, err := selectDefaultVariant(plan.CourseVariants, mode)
	if err != nil {
		return nil, err
	}
	return &VariantBindingView{
		ID:             selected.ID,
		Name:           selected.Name,
		RouteCode:      selected.RouteCode,
		AssignmentMode: mode,
	}, nil
}

func normalizeAssignmentMode(value string) *string {
	switch strings.TrimSpace(value) {
	case AssignmentModeManual:
		mode := AssignmentModeManual
		return &mode
	case AssignmentModeRandom:
		mode := AssignmentModeRandom
		return &mode
	case AssignmentModeServerAssigned:
		mode := AssignmentModeServerAssigned
		return &mode
	default:
		return nil
	}
}

func selectDefaultVariant(items []CourseVariantView, mode string) (*CourseVariantView, error) {
	candidates := make([]CourseVariantView, 0, len(items))
	for _, item := range items {
		if item.Selectable {
			candidates = append(candidates, item)
		}
	}
	if len(candidates) == 0 {
		candidates = append(candidates, items...)
	}
	if len(candidates) == 0 {
		return nil, apperr.New(http.StatusBadRequest, "variant_not_found", "course variants are empty")
	}

	switch mode {
	case AssignmentModeRandom:
		index, err := rand.Int(rand.Reader, big.NewInt(int64(len(candidates))))
		if err != nil {
			return nil, apperr.New(http.StatusInternalServerError, "variant_select_failed", fmt.Sprintf("failed to select random variant: %v", err))
		}
		selected := candidates[int(index.Int64())]
		return &selected, nil
	case AssignmentModeServerAssigned, AssignmentModeManual:
		fallthrough
	default:
		selected := candidates[0]
		return &selected, nil
	}
}
