package sync

import (
	"fmt"
	"time"

	"go.uber.org/zap"

	"github.com/google/uuid"
	"github.com/shopspring/decimal"

	"rmser/internal/domain/catalog"
	"rmser/internal/domain/invoices"
	"rmser/internal/domain/operations"
	"rmser/internal/domain/recipes"
	"rmser/internal/infrastructure/rms"
	"rmser/pkg/logger"
)

const (
	// Пресеты от пользователя
	PresetPurchases = "1a3297e1-cb05-55dc-98a7-c13f13bc85a7" // Закупки
	PresetUsage     = "24d9402e-2d01-eca1-ebeb-7981f7d1cb86" // Расход
)

type Service struct {
	rmsClient   rms.ClientI
	catalogRepo catalog.Repository
	recipeRepo  recipes.Repository
	invoiceRepo invoices.Repository
	opRepo      operations.Repository
}

func NewService(
	rmsClient rms.ClientI,
	catalogRepo catalog.Repository,
	recipeRepo recipes.Repository,
	invoiceRepo invoices.Repository,
	opRepo operations.Repository,
) *Service {
	return &Service{
		rmsClient:   rmsClient,
		catalogRepo: catalogRepo,
		recipeRepo:  recipeRepo,
		invoiceRepo: invoiceRepo,
		opRepo:      opRepo,
	}
}

// SyncCatalog загружает номенклатуру и сохраняет в БД
func (s *Service) SyncCatalog() error {
	logger.Log.Info("Начало синхронизации номенклатуры")

	products, err := s.rmsClient.FetchCatalog()
	if err != nil {
		return fmt.Errorf("ошибка получения каталога из RMS: %w", err)
	}

	if err := s.catalogRepo.SaveProducts(products); err != nil {
		return fmt.Errorf("ошибка сохранения продуктов в БД: %w", err)
	}

	logger.Log.Info("Синхронизация номенклатуры завершена", zap.Int("count", len(products)))
	return nil
}

// SyncRecipes загружает техкарты за указанный период (или за последние 30 дней по умолчанию)
func (s *Service) SyncRecipes() error {
	logger.Log.Info("Начало синхронизации техкарт")

	// RMS требует dateFrom. Берем широкий диапазон, например, с начала года или фиксированную дату,
	// либо можно сделать конфигурируемым. Для примера берем -3 месяца от текущей даты.
	// В реальном проде лучше брать дату последнего изменения, если API поддерживает revision,
	// но V2 API iiko часто требует полной перезагрузки актуальных карт.
	dateFrom := time.Now().AddDate(0, -3, 0)
	dateTo := time.Now() // +1 месяц вперед на случай будущих меню

	recipes, err := s.rmsClient.FetchRecipes(dateFrom, dateTo)
	if err != nil {
		return fmt.Errorf("ошибка получения техкарт из RMS: %w", err)
	}

	if err := s.recipeRepo.SaveRecipes(recipes); err != nil {
		return fmt.Errorf("ошибка сохранения техкарт в БД: %w", err)
	}

	logger.Log.Info("Синхронизация техкарт завершена", zap.Int("count", len(recipes)))
	return nil
}

// SyncInvoices загружает накладные. Если в базе пусто, грузит за последние N дней.
func (s *Service) SyncInvoices() error {
	logger.Log.Info("Начало синхронизации накладных")

	lastDate, err := s.invoiceRepo.GetLastInvoiceDate()
	if err != nil {
		return fmt.Errorf("ошибка получения даты последней накладной: %w", err)
	}

	var from time.Time
	to := time.Now()

	if lastDate != nil {
		// Берем следующий день после последней загрузки или тот же день, чтобы обновить изменения
		from = *lastDate
	} else {
		// Дефолтная загрузка за 30 дней назад
		from = time.Now().AddDate(0, 0, -30)
	}

	logger.Log.Info("Запрос накладных", zap.Time("from", from), zap.Time("to", to))

	invoices, err := s.rmsClient.FetchInvoices(from, to)
	if err != nil {
		return fmt.Errorf("ошибка получения накладных из RMS: %w", err)
	}

	if len(invoices) == 0 {
		logger.Log.Info("Новых накладных не найдено")
		return nil
	}

	if err := s.invoiceRepo.SaveInvoices(invoices); err != nil {
		return fmt.Errorf("ошибка сохранения накладных в БД: %w", err)
	}

	logger.Log.Info("Синхронизация накладных завершена", zap.Int("count", len(invoices)))
	return nil
}

// classifyOperation определяет тип операции на основе DocumentType
func classifyOperation(docType string) operations.OperationType {
	switch docType {
	// === ПРИХОД (PURCHASE) ===
	case "INCOMING_INVOICE": // Приходная накладная
		return operations.OpTypePurchase
	case "INCOMING_SERVICE": // Акт приема услуг (редко товары, но бывает)
		return operations.OpTypePurchase

	// === РАСХОД (USAGE) ===
	case "SALES_DOCUMENT": // Акт реализации (продажа)
		return operations.OpTypeUsage
	case "WRITEOFF_DOCUMENT": // Акт списания (порча, проработки)
		return operations.OpTypeUsage
	case "OUTGOING_INVOICE": // Расходная накладная
		return operations.OpTypeUsage
	case "SESSION_ACCEPTANCE": // Принятие смены (иногда агрегирует продажи)
		return operations.OpTypeUsage
	case "DISASSEMBLE_DOCUMENT": // Акт разбора (расход целого)
		return operations.OpTypeUsage

	// === Спорные/Игнорируемые ===
	// RETURNED_INVOICE (Возвратная накладная) - технически это уменьшение прихода,
	// но для рекомендаций "что мы покупаем" лучше обрабатывать отдельно или как минус-purchase.
	// Пока отнесем к UNKNOWN, чтобы не портить статистику чистого прихода,
	// либо можно считать как Purchase с отрицательным Amount (если XML дает минус).
	case "RETURNED_INVOICE":
		return operations.OpTypeUnknown

	case "INTERNAL_TRANSFER":
		return operations.OpTypeUnknown // Перемещение нас не интересует в рамках рекомендаций "купил/продал"
	case "INCOMING_INVENTORY":
		return operations.OpTypeUnknown // Инвентаризация

	default:
		return operations.OpTypeUnknown
	}
}

func (s *Service) SyncStoreOperations() error {
	dateTo := time.Now()
	dateFrom := dateTo.AddDate(0, 0, -30)

	// 1. Синхронизируем Закупки (PresetPurchases)
	// Мы передаем OpTypePurchase, чтобы репозиторий знал, какую "полку" очистить перед записью.
	if err := s.syncReport(PresetPurchases, operations.OpTypePurchase, dateFrom, dateTo); err != nil {
		return fmt.Errorf("ошибка синхронизации закупок: %w", err)
	}

	// 2. Синхронизируем Расход (PresetUsage)
	if err := s.syncReport(PresetUsage, operations.OpTypeUsage, dateFrom, dateTo); err != nil {
		return fmt.Errorf("ошибка синхронизации расхода: %w", err)
	}

	return nil
}

func (s *Service) syncReport(presetID string, targetOpType operations.OperationType, from, to time.Time) error {
	logger.Log.Info("Запрос отчета RMS", zap.String("preset", presetID))

	items, err := s.rmsClient.FetchStoreOperations(presetID, from, to)
	if err != nil {
		return err
	}

	var ops []operations.StoreOperation
	for _, item := range items {
		// 1. Валидация товара
		pID, err := uuid.Parse(item.ProductID)
		if err != nil {
			continue
		}

		// 2. Определение реального типа операции
		realOpType := classifyOperation(item.DocumentType)

		// 3. Фильтрация "мусора"
		// Если мы грузим отчет "Закупки", но туда попало "Перемещение" (из-за кривого пресета),
		// мы это пропустим. Либо если документ неизвестного типа.
		if realOpType == operations.OpTypeUnknown {
			continue
		}

		// Важно: Мы сохраняем только то, что соответствует целевому типу этапа синхронизации.
		// Если в пресете "Закупки" попалась "Реализация", мы не должны писать её в "Закупки",
		// и не должны писать в "Расход" (так как мы сейчас чистим "Закупки").
		if realOpType != targetOpType {
			continue
		}

		ops = append(ops, operations.StoreOperation{
			ProductID:       pID,
			OpType:          realOpType,
			DocumentType:    item.DocumentType,
			TransactionType: item.TransactionType,
			DocumentNumber:  item.DocumentNum,
			Amount:          decimal.NewFromFloat(item.Amount),
			Sum:             decimal.NewFromFloat(item.Sum),
			Cost:            decimal.NewFromFloat(item.Cost),
			PeriodFrom:      from,
			PeriodTo:        to,
		})
	}

	if err := s.opRepo.SaveOperations(ops, targetOpType, from, to); err != nil {
		return err
	}

	logger.Log.Info("Отчет сохранен",
		zap.String("op_type", string(targetOpType)),
		zap.Int("received", len(items)),
		zap.Int("saved", len(ops)))
	return nil
}
