package recipe

import (
	"context"
	"errors"
	"fmt"

	"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
	"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
	"git.huangwc.com/pig/pig-farm-controller/internal/infra/repository"

	"gorm.io/gorm"
)

// StockQuerier 定义了从外部领域查询库存的接口。
// 这样，配方领域就不需要知道库存是如何存储或计算的。
type StockQuerier interface {
	// GetCurrentStock 根据原料ID获取当前库存量。
	GetCurrentStock(ctx context.Context, rawMaterialID uint32) (float32, error)
}

// 定义领域特定的错误
var (
	ErrRawMaterialNameConflict  = fmt.Errorf("原料名称已存在")
	ErrRawMaterialNotFound      = fmt.Errorf("原料不存在")
	ErrStockNotEmpty            = fmt.Errorf("原料尚有库存,无法删除")
	ErrRawMaterialInUseByRecipe = fmt.Errorf("原料已被配方使用，无法删除")
)

// RawMaterialService 定义了原料领域的核心业务服务接口
type RawMaterialService interface {
	CreateRawMaterial(ctx context.Context, name, description string, referencePrice, maxAdditionRatio float32) (*models.RawMaterial, error)
	UpdateRawMaterial(ctx context.Context, id uint32, name, description string, referencePrice float32, maxAdditionRatio *float32) (*models.RawMaterial, error)
	DeleteRawMaterial(ctx context.Context, id uint32) error
	GetRawMaterial(ctx context.Context, id uint32) (*models.RawMaterial, error)
	ListRawMaterials(ctx context.Context, opts repository.RawMaterialListOptions, page, pageSize int) ([]models.RawMaterial, int64, error)
	UpdateRawMaterialNutrients(ctx context.Context, rawMaterialID uint32, nutrients []models.RawMaterialNutrient) error
}

// rawMaterialServiceImpl 是 RawMaterialService 的实现
type rawMaterialServiceImpl struct {
	ctx             context.Context
	uow             repository.UnitOfWork
	rawMaterialRepo repository.RawMaterialRepository
	stockQuerier    StockQuerier
}

// NewRawMaterialService 创建一个新的 RawMaterialService 实例
func NewRawMaterialService(ctx context.Context, uow repository.UnitOfWork, rawMaterialRepo repository.RawMaterialRepository, stockQuerier StockQuerier) RawMaterialService {
	return &rawMaterialServiceImpl{
		ctx:             ctx,
		uow:             uow,
		rawMaterialRepo: rawMaterialRepo,
		stockQuerier:    stockQuerier,
	}
}

// CreateRawMaterial 实现了创建原料的核心业务逻辑
func (s *rawMaterialServiceImpl) CreateRawMaterial(ctx context.Context, name, description string, referencePrice, maxAdditionRatio float32) (*models.RawMaterial, error) {
	serviceCtx := logs.AddFuncName(ctx, s.ctx, "CreateRawMaterial")

	// 检查名称是否已存在
	existing, err := s.rawMaterialRepo.GetRawMaterialByName(serviceCtx, name)
	if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
		return nil, fmt.Errorf("检查原料名称失败: %w", err)
	}
	if existing != nil {
		return nil, ErrRawMaterialNameConflict
	}

	rawMaterial := &models.RawMaterial{
		Name:             name,
		Description:      description,
		ReferencePrice:   referencePrice,
		MaxAdditionRatio: maxAdditionRatio,
	}

	if err := s.rawMaterialRepo.CreateRawMaterial(serviceCtx, rawMaterial); err != nil {
		return nil, fmt.Errorf("创建原料失败: %w", err)
	}

	return rawMaterial, nil
}

// UpdateRawMaterial 实现了更新原料的核心业务逻辑
func (s *rawMaterialServiceImpl) UpdateRawMaterial(ctx context.Context, id uint32, name, description string, referencePrice float32, maxAdditionRatio *float32) (*models.RawMaterial, error) {
	serviceCtx := logs.AddFuncName(ctx, s.ctx, "UpdateRawMaterial")

	// 检查要更新的实体是否存在
	rawMaterial, err := s.rawMaterialRepo.GetRawMaterialByID(serviceCtx, id)
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, ErrRawMaterialNotFound
		}
		return nil, fmt.Errorf("获取待更新的原料失败: %w", err)
	}

	// 如果名称有变动，检查新名称是否与其它记录冲突
	if rawMaterial.Name != name {
		existing, err := s.rawMaterialRepo.GetRawMaterialByName(serviceCtx, name)
		if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, fmt.Errorf("检查新的原料名称失败: %w", err)
		}
		if existing != nil && existing.ID != id {
			return nil, ErrRawMaterialNameConflict
		}
	}

	rawMaterial.Name = name
	rawMaterial.Description = description
	rawMaterial.ReferencePrice = referencePrice
	if maxAdditionRatio != nil {
		rawMaterial.MaxAdditionRatio = *maxAdditionRatio
	}

	if err := s.rawMaterialRepo.UpdateRawMaterial(serviceCtx, rawMaterial); err != nil {
		return nil, fmt.Errorf("更新原料失败: %w", err)
	}

	return rawMaterial, nil
}

// DeleteRawMaterial 实现了删除原料的核心业务逻辑
func (s *rawMaterialServiceImpl) DeleteRawMaterial(ctx context.Context, id uint32) error {
	serviceCtx := logs.AddFuncName(ctx, s.ctx, "DeleteRawMaterial")

	// 检查实体是否存在
	_, err := s.rawMaterialRepo.GetRawMaterialByID(serviceCtx, id)
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return ErrRawMaterialNotFound
		}
		return fmt.Errorf("获取待删除的原料失败: %w", err)
	}

	// 检查原料是否有库存
	stock, err := s.stockQuerier.GetCurrentStock(serviceCtx, id)
	if err != nil {
		return fmt.Errorf("检查原料库存失败: %w", err)
	}
	if stock > 0 {
		// 如果库存大于0，返回业务错误，阻止删除
		return ErrStockNotEmpty
	}

	// 检查原料是否被配方使用
	isUsed, err := s.rawMaterialRepo.IsRawMaterialUsedInRecipes(serviceCtx, id)
	if err != nil {
		return fmt.Errorf("检查原料是否被配方使用失败: %w", err)
	}
	if isUsed {
		return ErrRawMaterialInUseByRecipe
	}

	if err := s.rawMaterialRepo.DeleteRawMaterial(serviceCtx, id); err != nil {
		return fmt.Errorf("删除原料失败: %w", err)
	}

	return nil
}

// GetRawMaterial 实现了获取单个原料的逻辑
func (s *rawMaterialServiceImpl) GetRawMaterial(ctx context.Context, id uint32) (*models.RawMaterial, error) {
	serviceCtx := logs.AddFuncName(ctx, s.ctx, "GetRawMaterial")

	rawMaterial, err := s.rawMaterialRepo.GetRawMaterialByID(serviceCtx, id)
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, ErrRawMaterialNotFound
		}
		return nil, fmt.Errorf("获取原料失败: %w", err)
	}
	return rawMaterial, nil
}

// ListRawMaterials 实现了列出原料的逻辑
func (s *rawMaterialServiceImpl) ListRawMaterials(ctx context.Context, opts repository.RawMaterialListOptions, page, pageSize int) ([]models.RawMaterial, int64, error) {
	serviceCtx := logs.AddFuncName(ctx, s.ctx, "ListRawMaterials")

	rawMaterials, total, err := s.rawMaterialRepo.ListRawMaterials(serviceCtx, opts, page, pageSize)
	if err != nil {
		return nil, 0, fmt.Errorf("获取原料列表失败: %w", err)
	}
	return rawMaterials, total, nil
}

// UpdateRawMaterialNutrients 实现了全量更新原料营养成分的业务逻辑
func (s *rawMaterialServiceImpl) UpdateRawMaterialNutrients(ctx context.Context, rawMaterialID uint32, nutrients []models.RawMaterialNutrient) error {
	serviceCtx := logs.AddFuncName(ctx, s.ctx, "UpdateRawMaterialNutrients")

	// 1. 检查原料是否存在
	if _, err := s.rawMaterialRepo.GetRawMaterialByID(serviceCtx, rawMaterialID); err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return ErrRawMaterialNotFound
		}
		return fmt.Errorf("获取待更新的原料失败: %w", err)
	}

	// 2. 在事务中执行替换操作
	err := s.uow.ExecuteInTransaction(serviceCtx, func(tx *gorm.DB) error {
		// 2.1. 删除旧的关联记录
		if err := s.rawMaterialRepo.DeleteNutrientsByRawMaterialIDTx(serviceCtx, tx, rawMaterialID); err != nil {
			return err // 错误已在仓库层封装，直接返回
		}

		// 2.2. 创建新的关联记录
		if err := s.rawMaterialRepo.CreateBatchRawMaterialNutrientsTx(serviceCtx, tx, nutrients); err != nil {
			return err // 错误已在仓库层封装，直接返回
		}

		return nil
	})

	if err != nil {
		return fmt.Errorf("更新原料营养成分事务执行失败: %w", err)
	}

	// 3. 操作成功，直接返回 nil
	return nil
}
