package service

import (
	"context"
	"errors"
	"fmt"

	"git.huangwc.com/pig/pig-farm-controller/internal/app/dto"
	"git.huangwc.com/pig/pig-farm-controller/internal/domain/recipe"
	"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"
)

// 定义原料服务特定的错误
var (
	ErrRawMaterialNameConflict = errors.New("原料名称已存在")
	ErrRawMaterialNotFound     = errors.New("原料不存在")
)

// RawMaterialService 定义了原料相关的应用服务接口
type RawMaterialService interface {
	CreateRawMaterial(ctx context.Context, req *dto.CreateRawMaterialRequest) (*dto.RawMaterialResponse, error)
	UpdateRawMaterial(ctx context.Context, id uint32, req *dto.UpdateRawMaterialRequest) (*dto.RawMaterialResponse, error)
	DeleteRawMaterial(ctx context.Context, id uint32) error
	GetRawMaterial(ctx context.Context, id uint32) (*dto.RawMaterialResponse, error)
	ListRawMaterials(ctx context.Context, req *dto.ListRawMaterialRequest) (*dto.ListRawMaterialResponse, error)
	UpdateRawMaterialNutrients(ctx context.Context, id uint32, req *dto.UpdateRawMaterialNutrientsRequest) (*dto.RawMaterialResponse, error)
}

// rawMaterialServiceImpl 是 RawMaterialService 接口的实现
type rawMaterialServiceImpl struct {
	ctx       context.Context
	recipeSvc recipe.Service
}

// NewRawMaterialService 创建一个新的 RawMaterialService 实例
func NewRawMaterialService(ctx context.Context, recipeSvc recipe.Service) RawMaterialService {
	return &rawMaterialServiceImpl{
		ctx:       ctx,
		recipeSvc: recipeSvc,
	}
}

// CreateRawMaterial 创建原料
func (s *rawMaterialServiceImpl) CreateRawMaterial(ctx context.Context, req *dto.CreateRawMaterialRequest) (*dto.RawMaterialResponse, error) {
	serviceCtx := logs.AddFuncName(ctx, s.ctx, "CreateRawMaterial")

	rawMaterial, err := s.recipeSvc.CreateRawMaterial(serviceCtx, req.Name, req.Description, req.ReferencePrice, req.MaxAdditionRatio)
	if err != nil {
		if errors.Is(err, recipe.ErrRawMaterialNameConflict) {
			return nil, ErrRawMaterialNameConflict
		}
		return nil, fmt.Errorf("创建原料失败: %w", err)
	}

	return dto.ConvertRawMaterialToDTO(rawMaterial), nil
}

// UpdateRawMaterial 更新原料
func (s *rawMaterialServiceImpl) UpdateRawMaterial(ctx context.Context, id uint32, req *dto.UpdateRawMaterialRequest) (*dto.RawMaterialResponse, error) {
	serviceCtx := logs.AddFuncName(ctx, s.ctx, "UpdateRawMaterial")

	rawMaterial, err := s.recipeSvc.UpdateRawMaterial(serviceCtx, id, req.Name, req.Description, req.ReferencePrice, req.MaxAdditionRatio)
	if err != nil {
		if errors.Is(err, recipe.ErrRawMaterialNotFound) {
			return nil, ErrRawMaterialNotFound
		}
		if errors.Is(err, recipe.ErrRawMaterialNameConflict) {
			return nil, ErrRawMaterialNameConflict
		}
		return nil, fmt.Errorf("更新原料失败: %w", err)
	}
	return dto.ConvertRawMaterialToDTO(rawMaterial), nil
}

// DeleteRawMaterial 删除原料
func (s *rawMaterialServiceImpl) DeleteRawMaterial(ctx context.Context, id uint32) error {
	serviceCtx := logs.AddFuncName(ctx, s.ctx, "DeleteRawMaterial")
	err := s.recipeSvc.DeleteRawMaterial(serviceCtx, id)
	if err != nil {
		if errors.Is(err, recipe.ErrRawMaterialNotFound) {
			return ErrRawMaterialNotFound
		}
		return fmt.Errorf("删除原料失败: %w", err)
	}
	return nil
}

// GetRawMaterial 获取单个原料
func (s *rawMaterialServiceImpl) GetRawMaterial(ctx context.Context, id uint32) (*dto.RawMaterialResponse, error) {
	serviceCtx := logs.AddFuncName(ctx, s.ctx, "GetRawMaterial")

	rawMaterial, err := s.recipeSvc.GetRawMaterial(serviceCtx, id)
	if err != nil {
		if errors.Is(err, recipe.ErrRawMaterialNotFound) {
			return nil, ErrRawMaterialNotFound
		}
		return nil, fmt.Errorf("获取原料失败: %w", err)
	}
	return dto.ConvertRawMaterialToDTO(rawMaterial), nil
}

// ListRawMaterials 列出原料
func (s *rawMaterialServiceImpl) ListRawMaterials(ctx context.Context, req *dto.ListRawMaterialRequest) (*dto.ListRawMaterialResponse, error) {
	serviceCtx := logs.AddFuncName(ctx, s.ctx, "ListRawMaterials")

	opts := repository.RawMaterialListOptions{
		Name:              req.Name,
		NutrientName:      req.NutrientName,
		MinReferencePrice: req.MinReferencePrice,
		MaxReferencePrice: req.MaxReferencePrice,
		OrderBy:           req.OrderBy,
	}
	rawMaterials, total, err := s.recipeSvc.ListRawMaterials(serviceCtx, opts, req.Page, req.PageSize)
	if err != nil {
		return nil, fmt.Errorf("获取原料列表失败: %w", err)
	}

	return dto.ConvertRawMaterialListToDTO(rawMaterials, total, req.Page, req.PageSize), nil
}

// UpdateRawMaterialNutrients 全量更新原料的营养成分
func (s *rawMaterialServiceImpl) UpdateRawMaterialNutrients(ctx context.Context, id uint32, req *dto.UpdateRawMaterialNutrientsRequest) (*dto.RawMaterialResponse, error) {
	serviceCtx := logs.AddFuncName(ctx, s.ctx, "UpdateRawMaterialNutrients")

	// 1. 将 DTO 转换为领域模型
	nutrients := make([]models.RawMaterialNutrient, len(req.Nutrients))
	for i, item := range req.Nutrients {
		nutrients[i] = models.RawMaterialNutrient{
			RawMaterialID: id,
			NutrientID:    item.NutrientID,
			Value:         item.Value,
		}
	}

	// 2. 调用领域服务执行更新命令
	err := s.recipeSvc.UpdateRawMaterialNutrients(serviceCtx, id, nutrients)
	if err != nil {
		if errors.Is(err, recipe.ErrRawMaterialNotFound) {
			return nil, ErrRawMaterialNotFound
		}
		// 此处可以根据领域层可能返回的其他特定错误进行转换
		return nil, fmt.Errorf("更新原料营养成分失败: %w", err)
	}

	// 3. 更新成功后，调用查询服务获取最新的原料信息
	updatedRawMaterial, err := s.recipeSvc.GetRawMaterial(serviceCtx, id)
	if err != nil {
		if errors.Is(err, recipe.ErrRawMaterialNotFound) {
			// 理论上不应该发生，因为刚更新成功
			return nil, ErrRawMaterialNotFound
		}
		return nil, fmt.Errorf("更新后获取原料信息失败: %w", err)
	}

	// 4. 将领域模型转换为 DTO 并返回
	return dto.ConvertRawMaterialToDTO(updatedRawMaterial), nil
}
