package persona

import (
	"fmt"
	"math"
)

// PlausibilityLevel represents how biologically plausible a feature combination is.
type PlausibilityLevel string

const (
	PlausibilityHighlyPlausible PlausibilityLevel = "highly_plausible" // 50%+ probability
	PlausibilityPlausible       PlausibilityLevel = "plausible"        // 15-50% probability
	PlausibilityUnusual         PlausibilityLevel = "unusual"          // 5-15% probability
	PlausibilityRare            PlausibilityLevel = "rare"             // 1-5% probability
	PlausibilityImplausible     PlausibilityLevel = "implausible"      // < 1% probability
)

// PlausibilityViolation represents a single biological plausibility issue.
type PlausibilityViolation struct {
	Field       string            // e.g., "face.eye_color"
	Value       interface{}       // The actual value
	Ethnicity   EthnicityCode     // The ethnicity being validated against
	Level       PlausibilityLevel // How plausible this is
	Probability float64           // Actual probability (0.0-1.0)
	Message     string            // Human-readable explanation
}

// PlausibilityResult contains the validation results for DNA.
type PlausibilityResult struct {
	Valid      bool                    // True if no implausible violations
	Violations []PlausibilityViolation // All violations found
	Score      float64                 // Overall plausibility score (0-1)
}

// ValidateHumanPlausibility checks if face/hair features are biologically plausible
// for the given ethnicity. Returns violations for implausible combinations.
func ValidateHumanPlausibility(dna *DNA) *PlausibilityResult {
	if dna == nil {
		return &PlausibilityResult{
			Valid: true,
			Score: 1.0,
		}
	}

	result := &PlausibilityResult{
		Valid:      true,
		Violations: []PlausibilityViolation{},
		Score:      1.0,
	}

	ethnicity := dna.Identity.Ethnicity
	if ethnicity == "" || ethnicity == EthnicityMixed {
		// Mixed or unspecified ethnicity - skip plausibility checks
		return result
	}

	// Validate eye color
	if violation := validateEyeColor(dna.Face.EyeColor, ethnicity); violation != nil {
		result.Violations = append(result.Violations, *violation)
		if violation.Level == PlausibilityImplausible {
			result.Valid = false
		}
	}

	// Validate hair color
	if violation := validateHairColor(dna.Face.HairColor, ethnicity); violation != nil {
		result.Violations = append(result.Violations, *violation)
		if violation.Level == PlausibilityImplausible {
			result.Valid = false
		}
	}

	// Validate hair texture
	if violation := validateHairTexture(dna.Face.HairTexture, ethnicity); violation != nil {
		result.Violations = append(result.Violations, *violation)
		if violation.Level == PlausibilityImplausible {
			result.Valid = false
		}
	}

	// Validate skin tone
	if violation := validateSkinTone(dna.Face.SkinTone, ethnicity); violation != nil {
		result.Violations = append(result.Violations, *violation)
		if violation.Level == PlausibilityImplausible {
			result.Valid = false
		}
	}

	// Calculate overall plausibility score (geometric mean of probabilities)
	if len(result.Violations) > 0 {
		product := 1.0
		for _, v := range result.Violations {
			product *= v.Probability
		}
		numFeatures := 4.0 // eye_color, hair_color, hair_texture, skin_tone
		result.Score = math.Pow(product, 1.0/numFeatures)
	}

	return result
}

// validateEyeColor checks if eye color is plausible for the ethnicity.
func validateEyeColor(eyeColor EyeColorCategory, ethnicity EthnicityCode) *PlausibilityViolation {
	if eyeColor == "" {
		return nil
	}

	prob := getEyeColorProbability(eyeColor, ethnicity)
	level := getProbabilityLevel(prob)

	if level == PlausibilityUnusual || level == PlausibilityRare || level == PlausibilityImplausible {
		return &PlausibilityViolation{
			Field:       "face.eye_color",
			Value:       eyeColor,
			Ethnicity:   ethnicity,
			Level:       level,
			Probability: prob,
			Message:     fmt.Sprintf("Eye color %s is %s for %s ethnicity (%.1f%% probability)", eyeColor, level, ethnicity, prob*100),
		}
	}

	return nil
}

// validateHairColor checks if hair color is plausible for the ethnicity.
func validateHairColor(hairColor HairColorCategory, ethnicity EthnicityCode) *PlausibilityViolation {
	if hairColor == "" {
		return nil
	}

	prob := getHairColorProbability(hairColor, ethnicity)
	level := getProbabilityLevel(prob)

	if level == PlausibilityUnusual || level == PlausibilityRare || level == PlausibilityImplausible {
		return &PlausibilityViolation{
			Field:       "face.hair_color",
			Value:       hairColor,
			Ethnicity:   ethnicity,
			Level:       level,
			Probability: prob,
			Message:     fmt.Sprintf("Hair color %s is %s for %s ethnicity (%.1f%% probability)", hairColor, level, ethnicity, prob*100),
		}
	}

	return nil
}

// validateHairTexture checks if hair texture is plausible for the ethnicity.
func validateHairTexture(hairTexture HairTextureCategory, ethnicity EthnicityCode) *PlausibilityViolation {
	if hairTexture == "" {
		return nil
	}

	prob := getHairTextureProbability(hairTexture, ethnicity)
	level := getProbabilityLevel(prob)

	if level == PlausibilityUnusual || level == PlausibilityRare || level == PlausibilityImplausible {
		return &PlausibilityViolation{
			Field:       "face.hair_texture",
			Value:       hairTexture,
			Ethnicity:   ethnicity,
			Level:       level,
			Probability: prob,
			Message:     fmt.Sprintf("Hair texture %s is %s for %s ethnicity (%.1f%% probability)", hairTexture, level, ethnicity, prob*100),
		}
	}

	return nil
}

// validateSkinTone checks if skin tone is plausible for the ethnicity.
func validateSkinTone(skinTone SkinToneCategory, ethnicity EthnicityCode) *PlausibilityViolation {
	if skinTone == "" {
		return nil
	}

	if !isSkinTonePlausible(skinTone, ethnicity) {
		return &PlausibilityViolation{
			Field:       "face.skin_tone",
			Value:       skinTone,
			Ethnicity:   ethnicity,
			Level:       PlausibilityImplausible,
			Probability: 0.005,
			Message:     fmt.Sprintf("Skin tone %s is implausible for %s ethnicity", skinTone, ethnicity),
		}
	}

	return nil
}

// getProbabilityLevel converts a probability to a PlausibilityLevel.
func getProbabilityLevel(prob float64) PlausibilityLevel {
	if prob >= 0.5 {
		return PlausibilityHighlyPlausible
	} else if prob >= 0.15 {
		return PlausibilityPlausible
	} else if prob >= 0.05 {
		return PlausibilityUnusual
	} else if prob >= 0.01 {
		return PlausibilityRare
	} else {
		return PlausibilityImplausible
	}
}

// getEyeColorProbability returns the probability of an eye color for an ethnicity.
func getEyeColorProbability(eyeColor EyeColorCategory, ethnicity EthnicityCode) float64 {
	switch ethnicity {
	case EthnicityEastAsian, EthnicitySoutheastAsian:
		switch eyeColor {
		case EyeColorDarkBrown:
			return 0.85
		case EyeColorBrown:
			return 0.15
		case EyeColorHazel:
			return 0.005
		default:
			return 0.001
		}

	case EthnicitySouthAsian:
		switch eyeColor {
		case EyeColorDarkBrown:
			return 0.75
		case EyeColorBrown:
			return 0.20
		case EyeColorHazel:
			return 0.03
		case EyeColorAmber:
			return 0.02
		default:
			return 0.005
		}

	case EthnicityAfrican:
		switch eyeColor {
		case EyeColorDarkBrown:
			return 0.85
		case EyeColorBrown:
			return 0.13
		case EyeColorAmber:
			return 0.02
		default:
			return 0.002
		}

	case EthnicityHispanic:
		switch eyeColor {
		case EyeColorBrown:
			return 0.55
		case EyeColorDarkBrown:
			return 0.25
		case EyeColorHazel:
			return 0.12
		case EyeColorGreen:
			return 0.05
		case EyeColorAmber:
			return 0.03
		default:
			return 0.01
		}

	case EthnicityMiddleEastern:
		switch eyeColor {
		case EyeColorBrown:
			return 0.50
		case EyeColorDarkBrown:
			return 0.30
		case EyeColorHazel:
			return 0.10
		case EyeColorGreen:
			return 0.07
		case EyeColorAmber:
			return 0.03
		default:
			return 0.005
		}

	case EthnicityCaucasian:
		switch eyeColor {
		case EyeColorBlue:
			return 0.30
		case EyeColorBrown:
			return 0.25
		case EyeColorGreen:
			return 0.15
		case EyeColorHazel:
			return 0.15
		case EyeColorGray:
			return 0.10
		case EyeColorDarkBrown:
			return 0.05
		default:
			return 0.02
		}

	default:
		return 0.15 // Mixed/unknown - moderate probability
	}
}

// getHairColorProbability returns the probability of a hair color for an ethnicity.
func getHairColorProbability(hairColor HairColorCategory, ethnicity EthnicityCode) float64 {
	switch ethnicity {
	case EthnicityEastAsian, EthnicitySoutheastAsian:
		switch hairColor {
		case HairColorBlack:
			return 0.85
		case HairColorDarkBrown:
			return 0.12
		case HairColorBrown:
			return 0.03
		default:
			return 0.001
		}

	case EthnicitySouthAsian:
		switch hairColor {
		case HairColorBlack:
			return 0.85
		case HairColorDarkBrown:
			return 0.12
		case HairColorBrown:
			return 0.03
		default:
			return 0.001
		}

	case EthnicityAfrican:
		switch hairColor {
		case HairColorBlack:
			return 0.92
		case HairColorDarkBrown:
			return 0.08
		default:
			return 0.001
		}

	case EthnicityHispanic:
		switch hairColor {
		case HairColorBlack:
			return 0.50
		case HairColorDarkBrown:
			return 0.35
		case HairColorBrown:
			return 0.12
		case HairColorLightBrown:
			return 0.03
		default:
			return 0.005
		}

	case EthnicityMiddleEastern:
		switch hairColor {
		case HairColorBlack:
			return 0.65
		case HairColorDarkBrown:
			return 0.30
		case HairColorBrown:
			return 0.05
		default:
			return 0.005
		}

	case EthnicityCaucasian:
		switch hairColor {
		case HairColorBrown:
			return 0.35
		case HairColorLightBrown:
			return 0.20
		case HairColorBlonde:
			return 0.20
		case HairColorDarkBrown:
			return 0.12
		case HairColorAuburn:
			return 0.08
		case HairColorRed:
			return 0.05
		default:
			return 0.02
		}

	default:
		return 0.10
	}
}

// getHairTextureProbability returns the probability of a hair texture for an ethnicity.
func getHairTextureProbability(hairTexture HairTextureCategory, ethnicity EthnicityCode) float64 {
	switch ethnicity {
	case EthnicityEastAsian, EthnicitySoutheastAsian:
		switch hairTexture {
		case HairTextureStraight:
			return 0.85
		case HairTextureWavy:
			return 0.15
		case HairTextureCurly:
			return 0.005
		case HairTextureCoily, HairTextureKinky:
			return 0.001
		default:
			return 0.01
		}

	case EthnicitySouthAsian:
		switch hairTexture {
		case HairTextureWavy:
			return 0.40
		case HairTextureStraight:
			return 0.35
		case HairTextureCurly:
			return 0.25
		default:
			return 0.01
		}

	case EthnicityAfrican:
		switch hairTexture {
		case HairTextureCoily:
			return 0.50
		case HairTextureKinky:
			return 0.30
		case HairTextureCurly:
			return 0.20
		case HairTextureWavy:
			return 0.005
		case HairTextureStraight:
			return 0.001
		default:
			return 0.01
		}

	case EthnicityHispanic:
		switch hairTexture {
		case HairTextureWavy:
			return 0.40
		case HairTextureStraight:
			return 0.30
		case HairTextureCurly:
			return 0.30
		default:
			return 0.01
		}

	case EthnicityMiddleEastern:
		switch hairTexture {
		case HairTextureWavy:
			return 0.45
		case HairTextureStraight:
			return 0.30
		case HairTextureCurly:
			return 0.25
		default:
			return 0.01
		}

	case EthnicityCaucasian:
		switch hairTexture {
		case HairTextureStraight:
			return 0.40
		case HairTextureWavy:
			return 0.40
		case HairTextureCurly:
			return 0.20
		default:
			return 0.01
		}

	default:
		return 0.15
	}
}

// isSkinTonePlausible checks if a skin tone is plausible for an ethnicity.
func isSkinTonePlausible(skinTone SkinToneCategory, ethnicity EthnicityCode) bool {
	switch ethnicity {
	case EthnicityEastAsian, EthnicitySoutheastAsian:
		return skinTone == SkinToneFair || skinTone == SkinToneLight ||
			skinTone == SkinToneMedium || skinTone == SkinToneTan

	case EthnicitySouthAsian:
		return skinTone == SkinToneLight || skinTone == SkinToneMedium ||
			skinTone == SkinToneTan || skinTone == SkinToneBrown || skinTone == SkinToneDeep

	case EthnicityAfrican:
		return skinTone == SkinToneMedium || skinTone == SkinToneTan ||
			skinTone == SkinToneBrown || skinTone == SkinToneDarkBrown || skinTone == SkinToneDeep

	case EthnicityHispanic:
		return skinTone == SkinToneLight || skinTone == SkinToneMedium ||
			skinTone == SkinToneTan || skinTone == SkinToneBrown ||
			skinTone == SkinToneDeep || skinTone == SkinToneOlive

	case EthnicityMiddleEastern:
		return skinTone == SkinToneLight || skinTone == SkinToneMedium ||
			skinTone == SkinToneTan || skinTone == SkinToneOlive

	case EthnicityCaucasian:
		return skinTone == SkinToneFair || skinTone == SkinToneLight || skinTone == SkinToneMedium

	case EthnicityMixed:
		return true // Mixed allows all skin tones

	default:
		return true // Unknown allows all
	}
}
