package recommendations

import (
	"fmt"
	"strconv"
	"time"

	"rmser/internal/domain/operations"
	"rmser/internal/domain/recommendations"

	"gorm.io/gorm"
)

type pgRepository struct {
	db *gorm.DB
}

func NewRepository(db *gorm.DB) recommendations.Repository {
	return &pgRepository{db: db}
}

// --- Методы Хранения ---

func (r *pgRepository) SaveAll(items []recommendations.Recommendation) error {
	return r.db.Transaction(func(tx *gorm.DB) error {
		if err := tx.Session(&gorm.Session{AllowGlobalUpdate: true}).Unscoped().Delete(&recommendations.Recommendation{}).Error; err != nil {
			return err
		}
		if len(items) > 0 {
			if err := tx.CreateInBatches(items, 100).Error; err != nil {
				return err
			}
		}
		return nil
	})
}

func (r *pgRepository) GetAll() ([]recommendations.Recommendation, error) {
	var items []recommendations.Recommendation
	err := r.db.Find(&items).Error
	return items, err
}

// --- Методы Аналитики ---

// 1. Товары (GOODS/PREPARED), не используемые в техкартах
func (r *pgRepository) FindUnusedGoods() ([]recommendations.Recommendation, error) {
	var results []recommendations.Recommendation

	query := `
		SELECT 
			p.id as product_id, 
			p.name as product_name,
			'Товар не используется ни в одной техкарте' as reason,
			? as type
		FROM products p
		WHERE p.type IN ('GOODS', 'PREPARED') 
		  AND p.is_deleted = false  -- Проверка на удаление
		  AND p.id NOT IN (
		      SELECT DISTINCT product_id FROM recipe_items
		  )
		  AND p.id NOT IN (
		      SELECT DISTINCT product_id FROM recipes
		  )
		ORDER BY p.name ASC
	`

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

// 2. Закупается, но нет в техкартах
func (r *pgRepository) FindPurchasedButUnused(days int) ([]recommendations.Recommendation, error) {
	var results []recommendations.Recommendation
	dateFrom := time.Now().AddDate(0, 0, -days)

	query := `
		SELECT DISTINCT
			p.id as product_id,
			p.name as product_name,
			'Товар активно закупается, но не включен ни в одну техкарту' as reason,
			? as type
		FROM store_operations so
		JOIN products p ON so.product_id = p.id
		WHERE 
			so.op_type = ?           
			AND so.period_from >= ?
			AND p.is_deleted = false -- Проверка на удаление
			AND p.id NOT IN (        
				SELECT DISTINCT product_id FROM recipe_items
			)
		ORDER BY p.name ASC
	`

	if err := r.db.Raw(query, recommendations.TypePurchasedButUnused, operations.OpTypePurchase, dateFrom).Scan(&results).Error; err != nil {
		return nil, err
	}
	return results, nil
}

// 3. Ингредиенты в актуальных техкартах без закупок
func (r *pgRepository) FindNoIncomingIngredients(days int) ([]recommendations.Recommendation, error) {
	var results []recommendations.Recommendation
	dateFrom := time.Now().AddDate(0, 0, -days)

	query := `
		SELECT
			p.id as product_id,
			p.name as product_name,
			'Нет закупок (' || ? || ' дн). Входит в: ' || STRING_AGG(DISTINCT parent.name, ', ') as reason,
			? as type
		FROM recipe_items ri
		JOIN recipes r ON ri.recipe_id = r.id
		JOIN products p ON ri.product_id = p.id
		JOIN products parent ON r.product_id = parent.id
		WHERE 
			(r.date_to IS NULL OR r.date_to >= CURRENT_DATE)
			AND p.type = 'GOODS'
			AND p.is_deleted = false      -- Сам ингредиент не удален
			AND parent.is_deleted = false -- Блюдо, в которое он входит, не удалено
			AND p.id NOT IN (
				SELECT product_id 
				FROM store_operations 
				WHERE op_type = ? 
				  AND period_from >= ?
			)
		GROUP BY p.id, p.name
		ORDER BY p.name ASC
	`

	if err := r.db.Raw(query, strconv.Itoa(days), recommendations.TypeNoIncoming, operations.OpTypePurchase, dateFrom).Scan(&results).Error; err != nil {
		return nil, err
	}
	return results, nil
}

// 4. Товары, которые закупаем, но не расходуем ("Висяки")
func (r *pgRepository) FindStaleGoods(days int) ([]recommendations.Recommendation, error) {
	var results []recommendations.Recommendation
	dateFrom := time.Now().AddDate(0, 0, -days)

	query := `
		SELECT DISTINCT
			p.id as product_id,
			p.name as product_name,
			? as reason,
			? as type
		FROM store_operations so
		JOIN products p ON so.product_id = p.id
		WHERE 
			so.op_type = ?           
			AND so.period_from >= ?
			AND p.is_deleted = false -- Проверка на удаление
			AND p.id NOT IN (        
				SELECT product_id 
				FROM store_operations 
				WHERE op_type = ? 
				  AND period_from >= ?
			)
		ORDER BY p.name ASC
	`

	reason := fmt.Sprintf("Были закупки, но нет расхода за %d дн.", days)

	if err := r.db.Raw(query, reason, recommendations.TypeStale, operations.OpTypePurchase, dateFrom, operations.OpTypeUsage, dateFrom).
		Scan(&results).Error; err != nil {
		return nil, err
	}
	return results, nil
}

// 5. Блюдо используется в техкарте другого блюда
func (r *pgRepository) FindDishesInRecipes() ([]recommendations.Recommendation, error) {
	var results []recommendations.Recommendation

	query := `
		SELECT DISTINCT
			child.id as product_id,
			child.name as product_name,
			'Является Блюдом (DISH), но указан ингредиентом в: ' || parent.name as reason,
			? as type
		FROM recipe_items ri
		JOIN products child ON ri.product_id = child.id
		JOIN recipes r ON ri.recipe_id = r.id
		JOIN products parent ON r.product_id = parent.id
		WHERE 
			child.type = 'DISH'
			AND child.is_deleted = false   -- Вложенное блюдо не удалено
			AND parent.is_deleted = false  -- Родительское блюдо не удалено
			AND (r.date_to IS NULL OR r.date_to >= CURRENT_DATE)
		ORDER BY child.name ASC
	`

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

// 6. Есть расход (Usage), но нет прихода (Purchase)
func (r *pgRepository) FindUsageWithoutPurchase(days int) ([]recommendations.Recommendation, error) {
	var results []recommendations.Recommendation
	dateFrom := time.Now().AddDate(0, 0, -days)

	query := `
		SELECT DISTINCT
			p.id as product_id,
			p.name as product_name,
			? as reason,
			? as type
		FROM store_operations so
		JOIN products p ON so.product_id = p.id
		WHERE 
			so.op_type = ?            -- Есть расход (продажа/списание)
			AND so.period_from >= ?
			AND p.type = 'GOODS'      -- Только для товаров
			AND p.is_deleted = false  -- Товар жив
			AND p.id NOT IN (         -- Но не было закупок
				SELECT product_id 
				FROM store_operations 
				WHERE op_type = ? 
				  AND period_from >= ?
			)
		ORDER BY p.name ASC
	`

	reason := fmt.Sprintf("Товар расходуется (продажи/списания), но не закупался последние %d дн.", days)

	// Аргументы: reason, type, OpUsage, date, OpPurchase, date
	if err := r.db.Raw(query,
		reason,
		recommendations.TypeUsageNoIncoming,
		operations.OpTypeUsage,
		dateFrom,
		operations.OpTypePurchase,
		dateFrom,
	).Scan(&results).Error; err != nil {
		return nil, err
	}
	return results, nil
}
