package personagen

import (
	"context"
	"fmt"
	"log/slog"
	"strings"

	"git.threesix.ai/jordan/persona-community-5/pkg/mediagen"
	"git.threesix.ai/jordan/persona-community-5/pkg/persona"
)

// generateImage builds a HEIA prompt and calls the mediagen provider for one image position.
// The anchor bytes (position 1 image) are passed as a reference image for identity consistency.
func generateImage(
	ctx context.Context,
	mg *mediagen.Manager,
	spec *persona.PersonaSpec,
	imgSpec *persona.ImageSpec,
	anchor []byte,
	logger *slog.Logger,
) ([]byte, error) {
	if mg == nil {
		return nil, fmt.Errorf("mediagen not configured")
	}

	prompt := buildHEIAPrompt(spec, imgSpec)
	imgSpec.Prompt = prompt

	req := mediagen.ImageRequest{
		Prompt:      prompt,
		AspectRatio: "9:16",
	}

	// For all positions after position 1, use the anchor image as a reference.
	if imgSpec.Position > 1 && anchor != nil {
		req.ReferenceImage = anchor
		req.ReferenceMime = "image/png"
	}

	logger.Info("generating image position",
		"position", imgSpec.Position,
		"tier", imgSpec.TierName,
		"expression", imgSpec.Expression,
	)

	resp, err := mg.GenerateImage(ctx, req)
	if err != nil {
		return nil, fmt.Errorf("image provider error: %w", err)
	}
	if len(resp.Images) == 0 {
		return nil, fmt.Errorf("no images returned from provider for position %d", imgSpec.Position)
	}

	return resp.Images[0].Data, nil
}

// buildHEIAPrompt assembles the full HEIA (High-Engagement Influencer Aesthetic) prompt.
// Section order: [IDENTITY] [FACE] [BODY] [POSE] [CLOTHING] [SCENE] [CONSTRAINTS]
func buildHEIAPrompt(spec *persona.PersonaSpec, imgSpec *persona.ImageSpec) string {
	var sb strings.Builder

	sb.WriteString(buildIdentitySection(spec))
	sb.WriteString(" ")
	sb.WriteString(buildBiologicalSection(spec))
	sb.WriteString(" ")
	sb.WriteString(buildPoseSection(imgSpec))
	sb.WriteString(" ")
	sb.WriteString(buildClothingSection(spec, imgSpec))
	sb.WriteString(" ")
	sb.WriteString(buildSceneSection(imgSpec))
	sb.WriteString(" ")
	sb.WriteString(buildConstraintsSection(spec))

	return strings.TrimSpace(sb.String())
}

// buildIdentitySection creates the [IDENTITY] section.
// Example: "[IDENTITY] 26-year-old Korean woman, 5'4" (163cm), slender-athletic build."
// For mixed-race personas with a resolved heritage breakdown, produces e.g.
// "[IDENTITY] 26-year-old East Asian and Latina/Hispanic heritage woman, ..."
func buildIdentitySection(spec *persona.PersonaSpec) string {
	if spec.DNA == nil {
		return ""
	}
	id := spec.DNA.Identity
	body := spec.DNA.Body

	ethnicityDesc := ethnicitToAdj(id.Ethnicity)
	if id.SecondaryHeritage != nil {
		ethnicityDesc = fmt.Sprintf(
			"%s and %s heritage",
			ethnicitToAdj(id.PrimaryHeritage),
			ethnicitToAdj(*id.SecondaryHeritage),
		)
	}

	heightFt := cmToFeet(body.HeightCM)
	return fmt.Sprintf(
		"[IDENTITY] %d-year-old %s %s, %s (%dcm), %s build.",
		id.Age,
		ethnicityDesc,
		strings.ToLower(string(id.Gender)),
		heightFt,
		body.HeightCM,
		strings.ReplaceAll(string(body.Build), "_", "-"),
	)
}

// buildBiologicalSection creates [FACE] and [BODY] sections from DNA.
func buildBiologicalSection(spec *persona.PersonaSpec) string {
	if spec.DNA == nil {
		return ""
	}
	face := spec.DNA.Face
	body := spec.DNA.Body

	faceDesc := fmt.Sprintf(
		"[FACE] %s face with %s cheekbones, %s jawline, %s eyes (%s, %s, %s), %s nose, %s %s lips, %s brows, %s %s skin, %s %s hair.",
		string(face.FaceShape),
		string(face.Cheekbones),
		string(face.Jawline),
		string(face.EyeShape),
		string(face.EyeColor),
		string(face.EyeSize),
		string(face.EyeSpacing),
		string(face.NoseShape),
		string(face.LipFullness),
		string(face.LipShape),
		string(face.BrowShape),
		string(face.SkinTone),
		string(face.SkinUndertone),
		string(face.HairLength),
		string(face.HairTexture),
	)

	bodyDesc := fmt.Sprintf(
		"[BODY] %s build, %s shoulders, %.2f WHR, %s posture.",
		string(body.Build),
		string(body.ShoulderWidth),
		body.WHRatio,
		string(body.PostureType),
	)

	return faceDesc + " " + bodyDesc
}

// buildPoseSection creates the [POSE] section from the image spec.
func buildPoseSection(imgSpec *persona.ImageSpec) string {
	return fmt.Sprintf(
		"[POSE] %s distance, %s angle, %s, %s expression, %s.",
		imgSpec.Distance,
		imgSpec.Angle,
		imgSpec.SubjectPosition,
		imgSpec.Expression,
		imgSpec.Pose,
	)
}

// buildClothingSection creates the [CLOTHING] section.
func buildClothingSection(spec *persona.PersonaSpec, imgSpec *persona.ImageSpec) string {
	outfit := imgSpec.Outfit
	if outfit == "" {
		// Fall back to fashion context key pieces if outfit not populated.
		if imgSpec.FashionContext != "" {
			ctx := persona.FashionContextFor(persona.FashionContextName(imgSpec.FashionContext))
			if len(ctx.KeyPieces) > 0 {
				outfit = strings.Join(ctx.KeyPieces[:min(2, len(ctx.KeyPieces))], ", ")
			}
		}
	}

	clothingState := imgSpec.ClothingState
	if clothingState == "" {
		clothingState = "stylish"
	}

	return fmt.Sprintf("[CLOTHING] %s — %s style.", outfit, clothingState)
}

// buildSceneSection creates the [SCENE] section from the image spec.
func buildSceneSection(imgSpec *persona.ImageSpec) string {
	scene := imgSpec.Scene
	if scene == "" {
		scene = "neutral background, professional lighting"
	}
	return fmt.Sprintf("[SCENE] %s.", scene)
}

// buildConstraintsSection creates the [CONSTRAINTS] section with anatomical integrity rules.
func buildConstraintsSection(spec *persona.PersonaSpec) string {
	if spec.DNA == nil {
		return ""
	}
	genderUpper := strings.ToUpper(string(spec.DNA.Identity.Gender))

	return fmt.Sprintf(
		"[CONSTRAINTS] %s SUBJECT ONLY. Human body has EXACTLY 2 arms, 2 legs, 10 fingers total. "+
			"NO extra limbs, merged fingers, or anatomical errors. "+
			"Single coherent face, no duplicate facial features. "+
			"Maintain consistent skin tone throughout: %s %s skin.",
		genderUpper,
		string(spec.DNA.Face.SkinTone),
		string(spec.DNA.Face.SkinUndertone),
	)
}

// ── Conversion helpers ─────────────────────────────────────────────────────

// cmToFeet converts centimeters to a "5'5\"" style string.
func cmToFeet(cm int) string {
	totalInches := float64(cm) / 2.54
	feet := int(totalInches) / 12
	inches := int(totalInches) % 12
	return fmt.Sprintf(`%d'%d"`, feet, inches)
}

// ethnicitToAdj converts an EthnicityCode to a natural-language adjective.
func ethnicitToAdj(e persona.EthnicityCode) string {
	switch e {
	case persona.EthnicityEastAsian:
		return "East Asian"
	case persona.EthnicitySouthAsian:
		return "South Asian"
	case persona.EthnicitySoutheastAsian:
		return "Southeast Asian"
	case persona.EthnicityAfrican:
		return "Black"
	case persona.EthnicityHispanic:
		return "Latina/Hispanic"
	case persona.EthnicityMiddleEastern:
		return "Middle Eastern"
	case persona.EthnicityCaucasian:
		return "white"
	case persona.EthnicityMixed:
		return "mixed-race"
	default:
		return string(e)
	}
}
