package ocr

import (
	"context"
	"fmt"
	"strings"

	"github.com/google/uuid"
	"github.com/shopspring/decimal"
	"go.uber.org/zap"

	"rmser/internal/domain/catalog"
	"rmser/internal/domain/ocr"
	"rmser/internal/infrastructure/ocr_client"
	"rmser/pkg/logger"
)

type Service struct {
	ocrRepo     ocr.Repository
	catalogRepo catalog.Repository
	pyClient    *ocr_client.Client // Клиент к Python сервису
}

func NewService(
	ocrRepo ocr.Repository,
	catalogRepo catalog.Repository,
	pyClient *ocr_client.Client,
) *Service {
	return &Service{
		ocrRepo:     ocrRepo,
		catalogRepo: catalogRepo,
		pyClient:    pyClient,
	}
}

// ProcessReceiptImage - основной метод: Картинка -> Распознавание -> Матчинг
func (s *Service) ProcessReceiptImage(ctx context.Context, imgData []byte) ([]ProcessedItem, error) {
	// 1. Отправляем в Python
	rawResult, err := s.pyClient.ProcessImage(ctx, imgData, "receipt.jpg")
	if err != nil {
		return nil, fmt.Errorf("python ocr error: %w", err)
	}

	var processed []ProcessedItem

	// 2. Обрабатываем каждую строку
	for _, rawItem := range rawResult.Items {
		item := ProcessedItem{
			RawName: rawItem.RawName,
			Amount:  decimal.NewFromFloat(rawItem.Amount),
			Price:   decimal.NewFromFloat(rawItem.Price),
			Sum:     decimal.NewFromFloat(rawItem.Sum),
		}

		// 3. Ищем соответствие
		// Сначала проверяем таблицу ручного обучения (product_matches)
		matchID, err := s.ocrRepo.FindMatch(rawItem.RawName)
		if err != nil {
			logger.Log.Error("db error finding match", zap.Error(err))
		}

		if matchID != nil {
			// Нашли в обучении
			item.ProductID = matchID
			item.IsMatched = true
			item.MatchSource = "learned"
		} else {
			// Если не нашли, пробуем найти точное совпадение по имени в каталоге (на всякий случай)
			// (В реальном проекте тут может быть нечеткий поиск, но пока точный)
			// TODO: Добавить метод FindByName в репозиторий каталога, если нужно
		}

		processed = append(processed, item)
	}

	return processed, nil
}

// ProcessedItem - результат обработки одной строки чека
type ProcessedItem struct {
	RawName string
	Amount  decimal.Decimal
	Price   decimal.Decimal
	Sum     decimal.Decimal

	IsMatched   bool
	ProductID   *uuid.UUID
	MatchSource string // "learned", "auto", "manual"
}

// ProductForIndex DTO для внешнего сервиса
type ProductForIndex struct {
	ID   string `json:"id"`
	Name string `json:"name"`
	Code string `json:"code"`
}

// GetCatalogForIndexing возвращает список товаров для построения индекса
func (s *Service) GetCatalogForIndexing() ([]ProductForIndex, error) {
	products, err := s.catalogRepo.GetActiveGoods()
	if err != nil {
		return nil, err
	}

	result := make([]ProductForIndex, 0, len(products))
	for _, p := range products {
		result = append(result, ProductForIndex{
			ID:   p.ID.String(),
			Name: p.Name,
			Code: p.Code,
		})
	}
	return result, nil
}

// SaveMapping сохраняет связь "Текст из чека" -> "Наш товар"
func (s *Service) SaveMapping(rawName string, productID uuid.UUID) error {
	return s.ocrRepo.SaveMatch(rawName, productID)
}

// FindKnownMatch ищет, знаем ли мы уже этот товар
func (s *Service) FindKnownMatch(rawName string) (*uuid.UUID, error) {
	return s.ocrRepo.FindMatch(rawName)
}

// SearchProducts ищет товары в БД по части названия (для ручного выбора в боте)
func (s *Service) SearchProducts(query string) ([]catalog.Product, error) {
	// Этот метод нужно поддержать в репозитории, пока сделаем заглушку или фильтрацию в памяти
	// Для MVP добавим метод SearchByName в интерфейс репозитория
	all, err := s.catalogRepo.GetActiveGoods()
	if err != nil {
		return nil, err
	}

	// Простейший поиск в памяти (для начала хватит)
	query = strings.ToLower(query)
	var result []catalog.Product
	for _, p := range all {
		if strings.Contains(strings.ToLower(p.Name), query) {
			result = append(result, p)
			if len(result) >= 10 { // Ограничим выдачу
				break
			}
		}
	}
	return result, nil
}
