package character

import (
	"bamort/gsmaster"
	"bamort/models"
	"fmt"
	"net/http"
	"strconv"
	"strings"

	"github.com/gin-gonic/gin"
)

type SkillCostRequest struct {
	Name         string `json:"name" binding:"required"`                          // Name der Fertigkeit
	CurrentLevel int    `json:"current_level,omitempty"`                          // Aktueller Wert (nur für Verbesserung)
	Type         string `json:"type" binding:"required,oneof=skill spell weapon"` // 'skill', 'spell' oder 'weapon'
	Action       string `json:"action" binding:"required,oneof=learn improve"`    // 'learn' oder 'improve'
	TargetLevel  int    `json:"target_level,omitempty"`                           // Zielwert (optional, für Kostenberechnung bis zu einem bestimmten Level)
	UsePP        int    `json:"use_pp,omitempty"`                                 // Anzahl der zu verwendenden Praxispunkte

	// Belohnungsoptionen
	Reward *RewardOptions `json:"reward,omitempty"` // Belohnungsoptionen
}

// RewardOptions definiert die verschiedenen Belohnungsmöglichkeiten
type RewardOptions struct {
	Type         string `json:"type,omitempty" binding:"omitempty,oneof=free_learning free_spell_learning half_ep_improvement gold_for_ep"` // Art der Belohnung
	UseGoldForEP bool   `json:"use_gold_for_ep,omitempty"`                                                                                  // 10 GS statt 1 EP verwenden
	MaxGoldEP    int    `json:"max_gold_ep,omitempty"`                                                                                      // Maximale EP die durch Gold ersetzt werden (automatisch: Hälfte der Kosten)
}

type SkillCostResponse struct {
	*models.LearnCost
	SkillName    string `json:"skill_name"`
	SkillType    string `json:"skill_type"`
	Action       string `json:"action"`
	CharacterID  uint   `json:"character_id"`
	CurrentLevel int    `json:"current_level,omitempty"`
	TargetLevel  int    `json:"target_level,omitempty"`
	Category     string `json:"category,omitempty"`
	Difficulty   string `json:"difficulty,omitempty"`
	CanAfford    bool   `json:"can_afford"`
	Notes        string `json:"notes,omitempty"`
	PPUsed       int    `json:"pp_used,omitempty"`      // Anzahl der verwendeten Praxispunkte
	PPAvailable  int    `json:"pp_available,omitempty"` // Verfügbare Praxispunkte für diese Kategorie

	// Belohnungsdetails
	RewardApplied      string            `json:"reward_applied,omitempty"`       // Art der angewendeten Belohnung
	OriginalCostStruct *models.LearnCost `json:"original_cost_struct,omitempty"` // Ursprüngliche Kosten ohne Belohnung
	Savings            *models.LearnCost `json:"savings,omitempty"`              // Ersparnisse durch Belohnung
	GoldUsedForEP      int               `json:"gold_used_for_ep,omitempty"`     // Gold das für EP verwendet wurde
	PPReduction        int               `json:"pp_reduction,omitempty"`         // Reduktion der Kosten durch PP
	OriginalCost       int               `json:"original_cost,omitempty"`        // Ursprüngliche Kosten (vor PP-Reduktion)
	FinalCost          int               `json:"final_cost,omitempty"`           // Endgültige Kosten (nach PP-Reduktion)
}

type MultiLevelCostResponse struct {
	SkillName      string              `json:"skill_name"`
	SkillType      string              `json:"skill_type"`
	CharacterID    uint                `json:"character_id"`
	CurrentLevel   int                 `json:"current_level"`
	TargetLevel    int                 `json:"target_level"`
	LevelCosts     []SkillCostResponse `json:"level_costs"`
	TotalCost      *models.LearnCost   `json:"total_cost"`
	CanAffordTotal bool                `json:"can_afford_total"`
}

// GetLernCostNewSystem verwendet das neue Datenbank-Lernkosten-System
// und produziert die gleichen Ergebnisse wie GetLernCost.
//
// Unterschiede zum alten System:
// - Verwendet Models aus models/model_learning_costs.go statt der hardkodierten learningCostsData
// - Daten werden aus der Datenbank gelesen (learning_* Tabellen)
// - Unterstützt die gleichen Belohnungen und Parameter wie das alte System
// - API ist vollständig kompatibel mit GetLernCost
//
// Das neue System muss zuerst mit gsmaster.InitializeLearningCostsSystem() initialisiert werden.
func GetLernCostNewSystem(c *gin.Context) {
	// Request-Parameter abrufen
	var request gsmaster.LernCostRequest
	if err := c.ShouldBindJSON(&request); err != nil {
		respondWithError(c, http.StatusBadRequest, "Ungültige Anfrageparameter: "+err.Error())
		return
	}

	charID := fmt.Sprintf("%d", request.CharId)
	var character models.Char
	if err := character.FirstID(charID); err != nil {
		respondWithError(c, http.StatusNotFound, "Charakter nicht gefunden")
		return
	}

	// Verwende Klassenabkürzung wenn der Typ länger als 3 Zeichen ist
	var characterClass string
	if len(character.Typ) > 3 {
		characterClass = gsmaster.GetClassAbbreviationNewSystem(character.Typ)
	} else {
		characterClass = character.Typ
	}

	// Normalize skill/spell name (trim whitespace, proper case)
	skillName := strings.TrimSpace(request.Name)

	var response []gsmaster.SkillCostResultNew
	remainingPP := request.UsePP
	remainingGold := request.UseGold

	// Für "learn" Aktion: nur eine Berechnung, da Lernkosten einmalig sind
	if request.Action == "learn" {
		if request.Type == "spell" {
			// Spell learning logic
			spellInfo, err := models.GetSpellLearningInfoNewSystem(skillName, characterClass)
			if err != nil {
				respondWithError(c, http.StatusBadRequest, fmt.Sprintf("Zauber '%s' nicht gefunden oder nicht für Klasse '%s' verfügbar: %v", skillName, characterClass, err))
				return
			}

			levelResult := gsmaster.SkillCostResultNew{
				CharacterID:    charID,
				CharacterClass: characterClass,
				SkillName:      skillName,
				Category:       spellInfo.SchoolName,
				Difficulty:     fmt.Sprintf("Stufe %d", spellInfo.SpellLevel),
				TargetLevel:    1, // Lernkosten sind für das Erlernen des Zaubers (Level 1)
			}

			err = calculateSpellLearnCostNewSystem(&request, &levelResult, &remainingPP, &remainingGold, spellInfo)
			if err != nil {
				respondWithError(c, http.StatusBadRequest, "Fehler bei der Kostenberechnung: "+err.Error())
				return
			}

			response = append(response, levelResult)
		} else {
			// Skill learning logic
			skillInfo, err := models.GetSkillCategoryAndDifficultyNewSystem(skillName, characterClass)
			if err != nil {
				respondWithError(c, http.StatusBadRequest, fmt.Sprintf("Fertigkeit '%s' nicht gefunden oder nicht für Klasse '%s' verfügbar: %v", skillName, characterClass, err))
				return
			}

			levelResult := gsmaster.SkillCostResultNew{
				CharacterID:    charID,
				CharacterClass: characterClass,
				SkillName:      skillName,
				Category:       skillInfo.CategoryName,
				Difficulty:     skillInfo.DifficultyName,
				TargetLevel:    1, // Lernkosten sind für das Erlernen der Fertigkeit (Level 1)
			}

			err = calculateSkillLearnCostNewSystem(&request, &levelResult, &remainingPP, &remainingGold, skillInfo)
			if err != nil {
				respondWithError(c, http.StatusBadRequest, "Fehler bei der Kostenberechnung: "+err.Error())
				return
			}

			response = append(response, levelResult)
		}
	} else {
		// Für "improve" Aktion: berechne für jedes Level von current+1 bis 18
		// Improvement only works on skills, not spells
		skillInfo, err := models.GetSkillCategoryAndDifficultyNewSystem(skillName, characterClass)
		if err != nil {
			respondWithError(c, http.StatusBadRequest, fmt.Sprintf("Fertigkeit '%s' nicht gefunden oder nicht für Klasse '%s' verfügbar: %v", skillName, characterClass, err))
			return
		}

		for i := request.CurrentLevel + 1; i <= 18; i++ {
			levelResult := gsmaster.SkillCostResultNew{
				CharacterID:    charID,
				CharacterClass: characterClass,
				SkillName:      skillName,
				Category:       skillInfo.CategoryName,
				Difficulty:     skillInfo.DifficultyName,
				TargetLevel:    i,
			}

			err := CalculateSkillImproveCostNewSystem(&request, &levelResult, i, &remainingPP, &remainingGold, skillInfo)
			if err != nil {
				respondWithError(c, http.StatusBadRequest, "Fehler bei der Kostenberechnung: "+err.Error())
				return
			}
			// für die nächste Runde die PP und Gold reduzieren die zum Lernen genutzt werden sollen
			if levelResult.PPUsed > 0 {
				request.UsePP -= levelResult.PPUsed
				// Sicherstellen, dass PP nicht unter 0 fallen
				if request.UsePP < 0 {
					request.UsePP = 0
				}
			}
			if levelResult.GoldUsed > 0 {
				request.UseGold -= levelResult.GoldUsed
				// Sicherstellen, dass Gold nicht unter 0 fällt
				if request.UseGold < 0 {
					request.UseGold = 0
				}
			}
			response = append(response, levelResult)
		}
	}

	c.JSON(http.StatusOK, response)
}

// calculateCostNewSystem berechnet die Kosten für ein Level mit dem neuen Datenbank-System
func CalculateSkillImproveCostNewSystem(request *gsmaster.LernCostRequest, result *gsmaster.SkillCostResultNew, targetLevel int, remainingPP *int, remainingGold *int, skillInfo *models.SkillLearningInfo) error {
	// 1. Hole die TE-Kosten für die Verbesserung vom aktuellen Level
	teRequired, err := models.GetImprovementCost(skillInfo.SkillName, skillInfo.CategoryName, skillInfo.DifficultyName, targetLevel)
	if err != nil {
		return fmt.Errorf("verbesserungskosten nicht gefunden für %s (Level %d): %v", skillInfo.SkillName, targetLevel, err)
	}

	// 2. Hole die EP-Kosten pro TE für diese Klasse und Kategorie
	if skillInfo.EPPerTE == 0 {
		epPerTE, err := models.GetEPPerTEForClassAndCategory(result.CharacterClass, skillInfo.CategoryName)
		if err != nil {
			return fmt.Errorf("EP-Kosten pro TE nicht gefunden für Klasse %s, Kategorie %s: %v", result.CharacterClass, skillInfo.CategoryName, err)
		}
		skillInfo.EPPerTE = epPerTE
	}

	// 3. Setze die ursprünglichen TE-Kosten
	trainCost := teRequired

	// 4. Anwenden von Praxispunkten (PP) - Exakt wie im alten System
	ppUsed := 0
	if *remainingPP > 0 {
		if trainCost < *remainingPP {
			ppUsed = trainCost // Maximal so viele PP verwenden wie TE benötigt werden
			trainCost = 0      // Wenn PP alle TE abdecken, setze trainCost auf 0
		} else if *remainingPP > 0 {
			ppUsed = *remainingPP // Verwende alle verfügbaren PP
			trainCost -= ppUsed   // Reduziere TE um verwendete PP
		}

		result.PPUsed = ppUsed
		*remainingPP -= ppUsed

		if *remainingPP < 0 {
			*remainingPP = 0
		}
	}

	// 5. Berechne Kosten nach PP-Anwendung (wie im alten System)
	result.LE = trainCost
	result.EP = skillInfo.EPPerTE * trainCost
	result.GoldCost = trainCost * 20 // Wie im alten System: 20 Gold pro TE

	// 6. Anwenden von Belohnungen
	if request.Reward != nil {
		applyRewardNewSystem(result, request.Reward, result.EP)
	}

	// 7. Anwenden von Gold für EP (falls verfügbar) - Beschränkt auf EP/2
	goldUsed := 0
	if *remainingGold > 0 {
		// 10 Gold = 1 EP, aber maximal EP/2 kann durch Gold ersetzt werden
		maxEPFromGold := result.EP / 2
		epFromGold := *remainingGold / 10

		if epFromGold > maxEPFromGold {
			// Beschränke auf maximal EP/2
			epFromGold = maxEPFromGold
			goldUsed = epFromGold * 10
		} else {
			// Verwende das verfügbare Gold
			goldUsed = *remainingGold
		}

		// Reduziere EP um die durch Gold ersetzte Menge
		result.EP -= epFromGold
		result.GoldUsed = goldUsed
		*remainingGold -= goldUsed

		if *remainingGold < 0 {
			*remainingGold = 0
		}
	}

	return nil
}

// calculateSkillLearnCostNewSystem berechnet die Kosten für das Erlernen einer Fertigkeit (Action: "learn", Type: "skill")
func calculateSkillLearnCostNewSystem(request *gsmaster.LernCostRequest, result *gsmaster.SkillCostResultNew, remainingPP *int, remainingGold *int, skillInfo *models.SkillLearningInfo) error {
	// 1. Hole die EP-Kosten pro TE für diese Klasse und Kategorie
	epPerTE, err := models.GetEPPerTEForClassAndCategory(result.CharacterClass, skillInfo.CategoryName)
	if err != nil {
		return fmt.Errorf("EP-Kosten pro TE nicht gefunden für Klasse %s, Kategorie %s: %v", result.CharacterClass, skillInfo.CategoryName, err)
	}

	// 2. Verwende die Lernkosten (LE) direkt aus der skillInfo - diese enthält bereits alle benötigten Informationen
	learnCost := skillInfo.LearnCost

	// 3. Berechne Kosten nach Lernregeln (wie im alten System)
	result.LE = learnCost
	result.EP = epPerTE * result.LE * 3 // Faktor 3 beim Lernen!
	result.GoldCost = result.LE * 200   // 200 Gold pro LE (nicht 20 Gold pro TE)

	// 4. Anwenden von Belohnungen
	if request.Reward != nil {
		applyRewardNewSystem(result, request.Reward, result.EP)
	}

	// 5. Für Skill-Lernen: Keine PP oder Gold-für-EP Anwendung erlaubt (im Gegensatz zu Spell-Lernen)
	// PP und Gold bleiben unverändert, da sie bei Skill-Lernen nicht verwendet werden

	return nil
}

// applyRewardNewSystem wendet Belohnungen auf die Kosten an (neues System)
func applyRewardNewSystem(result *gsmaster.SkillCostResultNew, reward *string, originalEP int) {
	if reward == nil || *reward == "" {
		return
	}

	switch *reward {
	case "noGold":
		// Kostenlose Fertigkeiten: Nur Geld ist 0, EP bleiben
		result.GoldCost = 0

	case "halveep":
		// Halbe EP für Verbesserungen
		result.EP = result.EP / 2

	case "halveepnoGold":
		// Halbe EP und kein Gold
		result.EP = result.EP / 2
		result.GoldCost = 0

	case "default":
		// Keine Änderungen
		break

	default:
		// Unbekannte Belohnung - ignorieren
		break
	}
}

// calculateSpellLearnCostNewSystem berechnet die Kosten für das Erlernen eines Zaubers (Action: "learn", Type: "spell")
func calculateSpellLearnCostNewSystem(request *gsmaster.LernCostRequest, result *gsmaster.SkillCostResultNew, remainingPP *int, remainingGold *int, spellInfo *models.SpellLearningInfo) error {
	// 1. Setze die grundlegenden Zauber-Informationen
	result.Category = spellInfo.SchoolName
	result.Difficulty = fmt.Sprintf("Stufe %d", spellInfo.SpellLevel)

	// 2. Berechne die LE-Kosten basierend auf der Zaubergrad
	leRequired := spellInfo.LERequired

	// 3. Anwenden von PP (Practice Points): 1 PP = 1 LE Reduktion (bei Zauber-Lernen erlaubt)
	ppUsed := 0
	if *remainingPP > 0 {
		if leRequired <= *remainingPP {
			ppUsed = leRequired // Maximal so viele PP verwenden wie LE benötigt werden
			leRequired = 0      // Wenn PP alle LE abdecken
		} else {
			ppUsed = *remainingPP // Verwende alle verfügbaren PP
			leRequired -= ppUsed  // Reduziere LE um verwendete PP
		}

		result.PPUsed = ppUsed
		*remainingPP -= ppUsed

		if *remainingPP < 0 {
			*remainingPP = 0
		}
	}

	// 4. Setze die finalen LE-Kosten
	result.LE = leRequired

	// 5. Berechne EP-Kosten basierend auf LE und EP-pro-LE für diese Klasse/Schule
	result.EP = result.LE * spellInfo.EPPerLE

	// 6. Berechne Gold-Kosten (Beispiel: 100 Gold pro LE wie im alten System)
	result.GoldCost = result.LE * 100

	// 7. Anwenden von Belohnungen (spruchrolle, halveep, etc.)
	if request.Reward != nil {
		applySpellRewardNewSystem(result, request.Reward)
	}

	// 8. Gold-für-EP Konvertierung für Zauber-Lernen (erlaubt)
	goldUsed := 0
	if *remainingGold > 0 {
		// 10 Gold = 1 EP, aber maximal EP/2 kann durch Gold ersetzt werden
		maxEPFromGold := result.EP / 2
		epFromGold := *remainingGold / 10

		if epFromGold > maxEPFromGold {
			// Beschränke auf maximal EP/2
			epFromGold = maxEPFromGold
			goldUsed = epFromGold * 10
		} else {
			// Verwende das verfügbare Gold
			goldUsed = *remainingGold
		}

		// Reduziere EP um die durch Gold ersetzte Menge
		result.EP -= epFromGold
		result.GoldUsed = goldUsed
		*remainingGold -= goldUsed

		if *remainingGold < 0 {
			*remainingGold = 0
		}
	}

	return nil
}

// applySpellRewardNewSystem wendet zauber-spezifische Belohnungen an
func applySpellRewardNewSystem(result *gsmaster.SkillCostResultNew, reward *string) {
	if reward == nil || *reward == "" {
		return
	}

	switch *reward {
	case "spruchrolle":
		// Spruchrolle: 20 Gold für jeden Versuch und 1/3 EP-Kosten bei Erfolg
		result.GoldCost = 20
		result.EP = result.EP / 3

	case "halveep":
		// Halbe EP für Zauber-Lernen
		result.EP = result.EP / 2

	case "halveepnoGold":
		// Halbe EP und kein Gold
		result.EP = result.EP / 2
		result.GoldCost = 0

	case "noGold":
		// Nur Geld ist 0, EP bleiben
		result.GoldCost = 0

	case "default":
		// Keine Änderungen
		break

	default:
		// Unbekannte Belohnung - ignorieren
		break
	}
}

// Helper function to get current skill level from character
func getCurrentSkillLevel(character *models.Char, skillName, skillType string) int {
	switch skillType {
	case "skill":
		for _, skill := range character.Fertigkeiten {
			if skill.Name == skillName {
				return skill.Fertigkeitswert
			}
		}
	case "weapon":
		for _, skill := range character.Waffenfertigkeiten {
			if skill.Name == skillName {
				return skill.Fertigkeitswert
			}
		}
	case "spell":
		// Spells don't have levels in the same way
		return 0
	}
	return -1
}

// applyReward wendet Belohnungen auf die Kosten an
func applyReward(cost *models.LearnCost, request *SkillCostRequest) *models.LearnCost {
	if request.Reward == nil || request.Reward.Type == "" {
		return cost
	}

	newCost := *cost // Kopie der ursprünglichen Kosten

	switch request.Reward.Type {
	case "free_learning":
		// Kostenlose Fertigkeiten: Nur Geld ist 0, EP/LE bleiben
		if request.Type == "skill" && request.Action == "learn" {
			newCost.Money = 0
		}

	case "free_spell_learning":
		// Kostenlose Zauber: Nur LE ist 0, EP/Geld bleiben
		if request.Type == "spell" && request.Action == "learn" {
			newCost.LE = 0
		}

	case "half_ep_improvement":
		// Halbe EP für Verbesserungen
		if request.Action == "improve" {
			newCost.Ep = newCost.Ep / 2
		}

	case "gold_for_ep":
		// Gold statt EP verwenden (10 GS = 1 EP)
		if request.Reward.UseGoldForEP && newCost.Ep > 0 {
			maxGoldEP := request.Reward.MaxGoldEP
			if maxGoldEP == 0 {
				// Standard: Maximal die Hälfte der EP durch Gold ersetzen
				maxGoldEP = newCost.Ep / 2
			}

			// Beschränke auf verfügbare EP
			if maxGoldEP > newCost.Ep {
				maxGoldEP = newCost.Ep
			}

			// Ersetze EP durch Gold (10 GS pro EP)
			newCost.Ep -= maxGoldEP
			newCost.Money += maxGoldEP * 10
		}
	}

	return &newCost
}

// Helper structures and functions
type skillInfo struct {
	Category   string
	Difficulty string
}

func getSpellInfo(spellName string) skillInfo {
	var spell models.Spell
	if err := spell.First(spellName); err != nil {
		return skillInfo{Category: "unknown", Difficulty: "unknown"}
	}
	return skillInfo{Category: spell.Category, Difficulty: strconv.Itoa(spell.Stufe)}
}

func canCharacterAfford(character *models.Char, cost *models.LearnCost) bool {
	// Check if character has enough EP
	if character.Erfahrungsschatz.EP < cost.Ep {
		return false
	}

	// Check if character has enough money
	// Assuming money is stored in Bennies (Gold pieces)
	totalMoney := character.Bennies.Gg + character.Bennies.Gp + character.Bennies.Sg
	return totalMoney >= cost.Money
}

func generateNotes(character *models.Char, request *SkillCostRequest, cost *models.LearnCost) string {
	var notes []string

	if request.Action == "learn" {
		notes = append(notes, "Neue Fertigkeit erlernen")
	} else {
		notes = append(notes, fmt.Sprintf("Verbesserung von %d auf %d", request.CurrentLevel, request.CurrentLevel+1))
	}

	// Add character class specific notes
	if character.Typ != "" {
		notes = append(notes, fmt.Sprintf("Kosten für %s", character.Typ))
	}

	// Add PP usage notes
	if request.UsePP > 0 {
		notes = append(notes, fmt.Sprintf("Verwendung von %d Praxispunkten", request.UsePP))
	}

	// Add affordability note
	if !canCharacterAfford(character, cost) {
		notes = append(notes, "Nicht genügend EP oder Gold vorhanden")
	}

	return strings.Join(notes, ". ")
}

// getPPForSkill ermittelt die verfügbaren Praxispunkte für eine spezifische Fertigkeit
func getPPForSkill(character *models.Char, skillName string) int {
	// Ermittle die tatsächliche Fertigkeit (bei Zaubern die Zaubergruppe)
	targetSkillName := getSpellCategoryNewSystem(skillName)

	for _, fertigkeit := range character.Fertigkeiten {
		if fertigkeit.Name == targetSkillName {
			return fertigkeit.Pp
		}
	}
	return 0
}

// applyPPReduction reduziert die Kosten entsprechend der verwendeten Praxispunkte
func applyPPReduction(request *SkillCostRequest, cost *models.LearnCost, availablePP int) (int, int, int) {
	if request.UsePP <= 0 {
		return cost.Ep, cost.LE, 0
	}

	// Maximal so viele PP verwenden, wie verfügbar sind
	ppToUse := request.UsePP
	if ppToUse > availablePP {
		ppToUse = availablePP
	}

	originalEP := cost.Ep
	originalLE := cost.LE

	var finalEP, finalLE int
	var reduction int

	if request.Action == "improve" {
		// Für Verbesserungen: 1 TE für 1 PP
		// Jeder PP ersetzt 1 TE, daher wird die entsprechende EP-Menge reduziert
		reduction = ppToUse // PP-Punkte direkt als Reduktion verwenden
		finalEP = originalEP - reduction
		finalLE = originalLE
		if finalEP < 0 {
			finalEP = 0
		}
	} else if request.Action == "learn" && request.Type == "spell" {
		// Für Zauber lernen: 1 LE für 1 PP
		reduction = ppToUse // PP-Punkte direkt als Reduktion verwenden
		finalLE = originalLE - reduction
		finalEP = originalEP
		if finalLE < 0 {
			finalLE = 0
		}
	} else {
		// Für andere Lernfälle: keine PP-Reduktion
		finalEP = originalEP
		finalLE = originalLE
		reduction = 0
	}

	return finalEP, finalLE, reduction
}
