package inventory

import (
	"context"
	"errors"
	"fmt"
	"sync"
	"time"

	"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"
)

// 定义领域特定的错误
var (
	ErrRawMaterialNotFound = errors.New("原料不存在")
	ErrInsufficientStock   = errors.New("原料库存不足")
)

// InventoryCoreService 定义了库存领域的核心业务服务接口
type InventoryCoreService interface {
	// AdjustStock 调整指定原料的库存
	AdjustStock(ctx context.Context, rawMaterialID uint32, changeAmount float32, sourceType models.StockLogSourceType, sourceID *uint32, remarks string) (*models.RawMaterialStockLog, error)
	// GetCurrentStock 获取单个原料的当前库存量
	GetCurrentStock(ctx context.Context, rawMaterialID uint32) (float32, error)
	// BatchGetCurrentStock 批量获取多个原料的当前库存量
	BatchGetCurrentStock(ctx context.Context, rawMaterialIDs []uint32) (map[uint32]float32, error)
	// ListStockLogs 分页查询库存变动日志
	ListStockLogs(ctx context.Context, opts repository.StockLogListOptions, page, pageSize int) ([]models.RawMaterialStockLog, int64, error)
}

// inventoryCoreServiceImpl 是 InventoryCoreService 的实现
type inventoryCoreServiceImpl struct {
	ctx        context.Context
	uow        repository.UnitOfWork
	rawMatRepo repository.RawMaterialRepository

	// 全局库存调整锁，确保所有 AdjustStock 操作串行执行
	adjustStockMutex sync.Mutex
}

// NewInventoryCoreService 创建一个新的 InventoryCoreService 实例
func NewInventoryCoreService(ctx context.Context, uow repository.UnitOfWork, rawMatRepo repository.RawMaterialRepository) InventoryCoreService {
	return &inventoryCoreServiceImpl{
		ctx:        ctx,
		uow:        uow,
		rawMatRepo: rawMatRepo,
	}
}

// AdjustStock 调整指定原料的库存
func (s *inventoryCoreServiceImpl) AdjustStock(ctx context.Context, rawMaterialID uint32, changeAmount float32, sourceType models.StockLogSourceType, sourceID *uint32, remarks string) (*models.RawMaterialStockLog, error) {
	serviceCtx := logs.AddFuncName(ctx, s.ctx, "AdjustStock")

	// 使用全局锁确保所有库存调整操作串行执行
	s.adjustStockMutex.Lock()
	defer s.adjustStockMutex.Unlock()

	var createdLog *models.RawMaterialStockLog

	err := s.uow.ExecuteInTransaction(serviceCtx, func(tx *gorm.DB) error {
		// 在事务中创建 RawMaterialRepository 的新实例
		txRawMatRepo := repository.NewGormRawMaterialRepository(serviceCtx, tx)

		// 1. 检查原料是否存在
		_, err := txRawMatRepo.GetRawMaterialByID(serviceCtx, rawMaterialID)
		if err != nil {
			if errors.Is(err, gorm.ErrRecordNotFound) {
				return ErrRawMaterialNotFound
			}
			return fmt.Errorf("检查原料是否存在时出错: %w", err)
		}

		// 2. 获取当前库存 (在程序锁的保护下，这里是安全的)
		latestLog, err := txRawMatRepo.GetLatestRawMaterialStockLog(serviceCtx, rawMaterialID)
		if err != nil {
			return fmt.Errorf("获取最新库存日志失败: %w", err)
		}

		var beforeQuantity float32 = 0
		if latestLog != nil {
			beforeQuantity = latestLog.AfterQuantity
		}

		// 3. 计算新库存并检查是否充足
		afterQuantity := beforeQuantity + changeAmount
		if afterQuantity < 0 {
			return ErrInsufficientStock
		}

		// 4. 创建新的库存日志
		newLog := &models.RawMaterialStockLog{
			RawMaterialID:  rawMaterialID,
			ChangeAmount:   changeAmount,
			BeforeQuantity: beforeQuantity,
			AfterQuantity:  afterQuantity,
			SourceType:     sourceType,
			SourceID:       sourceID,
			HappenedAt:     time.Now(),
			Remarks:        remarks,
		}

		if err := txRawMatRepo.CreateRawMaterialStockLog(serviceCtx, newLog); err != nil {
			return fmt.Errorf("创建库存日志失败: %w", err)
		}

		createdLog = newLog
		return nil
	})

	if err != nil {
		return nil, err // 直接返回事务中发生的错误
	}

	return createdLog, nil
}

// GetCurrentStock 获取单个原料的当前库存量
func (s *inventoryCoreServiceImpl) GetCurrentStock(ctx context.Context, rawMaterialID uint32) (float32, error) {
	serviceCtx := logs.AddFuncName(ctx, s.ctx, "GetCurrentStock")

	latestLog, err := s.rawMatRepo.GetLatestRawMaterialStockLog(serviceCtx, rawMaterialID)
	if err != nil {
		return 0, fmt.Errorf("获取最新库存日志失败: %w", err)
	}

	if latestLog == nil {
		// 如果没有日志，说明从未入库，库存为0
		return 0, nil
	}

	return latestLog.AfterQuantity, nil
}

// BatchGetCurrentStock 批量获取多个原料的当前库存量
func (s *inventoryCoreServiceImpl) BatchGetCurrentStock(ctx context.Context, rawMaterialIDs []uint32) (map[uint32]float32, error) {
	serviceCtx := logs.AddFuncName(ctx, s.ctx, "BatchGetCurrentStock")

	logMap, err := s.rawMatRepo.BatchGetLatestStockLogsForMaterials(serviceCtx, rawMaterialIDs)
	if err != nil {
		return nil, fmt.Errorf("批量获取最新库存日志失败: %w", err)
	}

	stockMap := make(map[uint32]float32, len(rawMaterialIDs))
	for _, id := range rawMaterialIDs {
		if log, ok := logMap[id]; ok {
			stockMap[id] = log.AfterQuantity
		} else {
			// 如果某个原料在 logMap 中不存在，说明它没有任何库存记录，库存为0
			stockMap[id] = 0
		}
	}

	return stockMap, nil
}

// ListStockLogs 分页查询库存变动日志
func (s *inventoryCoreServiceImpl) ListStockLogs(ctx context.Context, opts repository.StockLogListOptions, page, pageSize int) ([]models.RawMaterialStockLog, int64, error) {
	serviceCtx := logs.AddFuncName(ctx, s.ctx, "ListStockLogs")

	logs, total, err := s.rawMatRepo.ListStockLogs(serviceCtx, opts, page, pageSize)
	if err != nil {
		return nil, 0, fmt.Errorf("获取库存日志列表失败: %w", err)
	}

	return logs, total, nil
}
