package repository

import (
	"context"
	"go-server/internal/models"

	"gorm.io/gorm"
)

type DailyOverviewRepository struct {
	db *gorm.DB
}

func NewDailyOverviewRepository(db *gorm.DB) *DailyOverviewRepository {
	return &DailyOverviewRepository{db: db}
}

// NutrientTotal represents the aggregated total for a nutrient across all supplements
type NutrientTotal struct {
	NutrientID   models.ULID `json:"nutrientId" db:"nutrient_id"`
	NutrientName string      `json:"nutrientName" db:"nutrient_name"`
	Description  string      `json:"description" db:"description"`
	TotalAmount  string      `json:"totalAmount" db:"total_amount"`
	Unit         string      `json:"unit" db:"unit"`
	Categories   []string    `json:"categories" db:"categories"`
}

// SupplementNutrientDetails represents detailed breakdown by supplement
type SupplementNutrientDetails struct {
	SupplementID    models.ULID `json:"supplementId" db:"supplement_id"`
	SupplementName  string      `json:"supplementName" db:"supplement_name"`
	NutrientID      models.ULID `json:"nutrientId" db:"nutrient_id"`
	NutrientName    string      `json:"nutrientName" db:"nutrient_name"`
	Amount          string      `json:"amount" db:"amount"`
	Unit            string      `json:"unit" db:"unit"`
	ServingSize     string      `json:"servingSize" db:"serving_size"`
	ReferenceIntake string      `json:"referenceIntake" db:"reference_intake"`
}

// GetNutrientTotals returns aggregated nutrient totals across all supplements
// This is perfect for a daily overview where you want to see total intake
func (r *DailyOverviewRepository) GetNutrientTotals(ctx context.Context) ([]NutrientTotal, error) {
	var results []NutrientTotal

	query := `
		SELECT 
			n.id as nutrient_id,
			n.name as nutrient_name,
			n.description,
			sn.per_serving_reference_intake,
			-- For now, we'll concatenate amounts (later we can parse and sum numeric values)
			STRING_AGG(sn.per_serving, ' + ') as total_amount,
			-- Extract unit from first entry (assumption: same nutrient has same unit)
			SPLIT_PART(MIN(sn.per_serving), ' ', 2) as unit,
			-- Get all categories for this nutrient
			ARRAY_AGG(DISTINCT c.name) as categories
		FROM nutrients n
		LEFT JOIN supplement_nutrients sn ON n.id = sn.nutrient_id
		LEFT JOIN supplements s ON sn.supplement_id = s.id
		LEFT JOIN nutrient_categories nc ON n.id = nc.nutrient_id
		LEFT JOIN categories c ON nc.category_id = c.id
		WHERE sn.id IS NOT NULL  -- Only nutrients that are in supplements
		GROUP BY n.id, n.name, n.description, sn.per_serving_reference_intake
		ORDER BY n.name
	`

	err := r.db.WithContext(ctx).Raw(query).Scan(&results).Error
	return results, err
}

// GetSupplementBreakdown returns detailed breakdown of nutrients by supplement
func (r *DailyOverviewRepository) GetSupplementBreakdown(ctx context.Context) ([]SupplementNutrientDetails, error) {
	var results []SupplementNutrientDetails

	query := `
		SELECT 
			s.id as supplement_id,
			s.name as supplement_name,
			n.id as nutrient_id,
			n.name as nutrient_name,
			sn.per_serving as amount,
			SPLIT_PART(sn.per_serving, ' ', 2) as unit,
			sn.serving_size,
			sn.per_serving_reference_intake as reference_intake
		FROM supplements s
		JOIN supplement_nutrients sn ON s.id = sn.supplement_id
		JOIN nutrients n ON sn.nutrient_id = n.id
		ORDER BY s.name, n.name
	`

	err := r.db.WithContext(ctx).Raw(query).Scan(&results).Error
	return results, err
}

// GetNutrientsByCategory returns nutrients grouped by category with totals
func (r *DailyOverviewRepository) GetNutrientsByCategory(ctx context.Context) (map[string][]NutrientTotal, error) {
	var results []struct {
		CategoryName string      `db:"category_name"`
		CategoryID   models.ULID `db:"category_id"`
		NutrientID   models.ULID `db:"nutrient_id"`
		NutrientName string      `db:"nutrient_name"`
		Description  string      `db:"description"`
		TotalAmount  string      `db:"total_amount"`
		Unit         string      `db:"unit"`
	}

	query := `
		SELECT 
			c.name as category_name,
			c.id as category_id,
			n.id as nutrient_id,
			n.name as nutrient_name,
			n.description,
			STRING_AGG(sn.per_serving, ' + ') as total_amount,
			SPLIT_PART(MIN(sn.per_serving), ' ', 2) as unit
		FROM categories c
		JOIN nutrient_categories nc ON c.id = nc.category_id
		JOIN nutrients n ON nc.nutrient_id = n.id
		LEFT JOIN supplement_nutrients sn ON n.id = sn.nutrient_id
		WHERE sn.id IS NOT NULL
		GROUP BY c.name, n.id, n.name, n.description, c.id
		ORDER BY c.name, n.name
	`

	err := r.db.WithContext(ctx).Raw(query).Scan(&results).Error
	if err != nil {
		return nil, err
	}

	// Group by category
	categoryMap := make(map[string][]NutrientTotal)
	for _, result := range results {
		nutrient := NutrientTotal{
			NutrientID:   result.NutrientID,
			NutrientName: result.NutrientName,
			Description:  result.Description,
			TotalAmount:  result.TotalAmount,
			Unit:         result.Unit,
			Categories:   []string{result.CategoryID.String()},
		}
		categoryMap[result.CategoryName] = append(categoryMap[result.CategoryName], nutrient)
	}

	return categoryMap, nil
}

// ExecuteRawQuery allows executing arbitrary SQL queries and scanning into any struct
// This gives you full flexibility for custom queries
func (r *DailyOverviewRepository) ExecuteRawQuery(ctx context.Context, query string, dest interface{}, args ...interface{}) error {
	return r.db.WithContext(ctx).Raw(query, args...).Scan(dest).Error
}

// ExecuteRawQueryWithResult executes a raw query and returns the result as a map
// Useful for dynamic queries where you don't know the structure ahead of time
func (r *DailyOverviewRepository) ExecuteRawQueryWithResult(ctx context.Context, query string, args ...interface{}) ([]map[string]interface{}, error) {
	rows, err := r.db.WithContext(ctx).Raw(query, args...).Rows()
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	columns, err := rows.Columns()
	if err != nil {
		return nil, err
	}

	var results []map[string]interface{}
	for rows.Next() {
		values := make([]interface{}, len(columns))
		valuePtrs := make([]interface{}, len(columns))
		for i := range columns {
			valuePtrs[i] = &values[i]
		}

		if err := rows.Scan(valuePtrs...); err != nil {
			return nil, err
		}

		result := make(map[string]interface{})
		for i, col := range columns {
			result[col] = values[i]
		}
		results = append(results, result)
	}

	return results, nil
}
