package repository

import (
	"git.mchus.pro/mchus/quoteforge/internal/localdb"
	"gorm.io/gorm"
	"gorm.io/gorm/clause"
)

// PartnumberBookRepository provides read-only access to local partnumber book snapshots.
type PartnumberBookRepository struct {
	db *gorm.DB
}

func NewPartnumberBookRepository(db *gorm.DB) *PartnumberBookRepository {
	return &PartnumberBookRepository{db: db}
}

// GetActiveBook returns the most recently active local partnumber book.
func (r *PartnumberBookRepository) GetActiveBook() (*localdb.LocalPartnumberBook, error) {
	var book localdb.LocalPartnumberBook
	err := r.db.Where("is_active = 1").Order("created_at DESC, id DESC").First(&book).Error
	if err != nil {
		return nil, err
	}
	return &book, nil
}

// GetBookItems returns all items for the given local book ID.
func (r *PartnumberBookRepository) GetBookItems(bookID uint) ([]localdb.LocalPartnumberBookItem, error) {
	book, err := r.getBook(bookID)
	if err != nil {
		return nil, err
	}
	items, _, err := r.listCatalogItems(book.PartnumbersJSON, "", 0, 0)
	return items, err
}

// GetBookItemsPage returns items for the given local book ID with optional search and pagination.
func (r *PartnumberBookRepository) GetBookItemsPage(bookID uint, search string, page, perPage int) ([]localdb.LocalPartnumberBookItem, int64, error) {
	if page < 1 {
		page = 1
	}
	if perPage < 1 {
		perPage = 100
	}

	book, err := r.getBook(bookID)
	if err != nil {
		return nil, 0, err
	}
	return r.listCatalogItems(book.PartnumbersJSON, search, page, perPage)
}

// FindLotByPartnumber looks up a partnumber in the active book and returns the matching items.
func (r *PartnumberBookRepository) FindLotByPartnumber(bookID uint, partnumber string) ([]localdb.LocalPartnumberBookItem, error) {
	book, err := r.getBook(bookID)
	if err != nil {
		return nil, err
	}
	found := false
	for _, pn := range book.PartnumbersJSON {
		if pn == partnumber {
			found = true
			break
		}
	}
	if !found {
		return nil, nil
	}
	var items []localdb.LocalPartnumberBookItem
	err = r.db.Where("partnumber = ?", partnumber).Find(&items).Error
	return items, err
}

// ListBooks returns all local partnumber books ordered newest first.
func (r *PartnumberBookRepository) ListBooks() ([]localdb.LocalPartnumberBook, error) {
	var books []localdb.LocalPartnumberBook
	err := r.db.Order("created_at DESC, id DESC").Find(&books).Error
	return books, err
}

// SaveBook saves a new partnumber book snapshot.
func (r *PartnumberBookRepository) SaveBook(book *localdb.LocalPartnumberBook) error {
	return r.db.Save(book).Error
}

// SaveBookItems upserts canonical PN catalog rows.
func (r *PartnumberBookRepository) SaveBookItems(items []localdb.LocalPartnumberBookItem) error {
	if len(items) == 0 {
		return nil
	}
	return r.db.Clauses(clause.OnConflict{
		Columns: []clause.Column{{Name: "partnumber"}},
		DoUpdates: clause.AssignmentColumns([]string{
			"lots_json",
			"description",
		}),
	}).CreateInBatches(items, 500).Error
}

// CountBookItems returns the number of items for a given local book ID.
func (r *PartnumberBookRepository) CountBookItems(bookID uint) int64 {
	book, err := r.getBook(bookID)
	if err != nil {
		return 0
	}
	return int64(len(book.PartnumbersJSON))
}

func (r *PartnumberBookRepository) CountDistinctLots(bookID uint) int64 {
	items, err := r.GetBookItems(bookID)
	if err != nil {
		return 0
	}
	seen := make(map[string]struct{})
	for _, item := range items {
		for _, lot := range item.LotsJSON {
			if lot.LotName == "" {
				continue
			}
			seen[lot.LotName] = struct{}{}
		}
	}
	return int64(len(seen))
}

func (r *PartnumberBookRepository) HasAllBookItems(bookID uint) bool {
	book, err := r.getBook(bookID)
	if err != nil {
		return false
	}
	if len(book.PartnumbersJSON) == 0 {
		return true
	}
	var count int64
	if err := r.db.Model(&localdb.LocalPartnumberBookItem{}).
		Where("partnumber IN ?", []string(book.PartnumbersJSON)).
		Count(&count).Error; err != nil {
		return false
	}
	return count == int64(len(book.PartnumbersJSON))
}

func (r *PartnumberBookRepository) getBook(bookID uint) (*localdb.LocalPartnumberBook, error) {
	var book localdb.LocalPartnumberBook
	if err := r.db.First(&book, bookID).Error; err != nil {
		return nil, err
	}
	return &book, nil
}

func (r *PartnumberBookRepository) listCatalogItems(partnumbers localdb.LocalStringList, search string, page, perPage int) ([]localdb.LocalPartnumberBookItem, int64, error) {
	if len(partnumbers) == 0 {
		return []localdb.LocalPartnumberBookItem{}, 0, nil
	}

	query := r.db.Model(&localdb.LocalPartnumberBookItem{}).Where("partnumber IN ?", []string(partnumbers))
	if search != "" {
		trimmedSearch := "%" + search + "%"
		query = query.Where("partnumber LIKE ? OR CAST(lots_json AS TEXT) LIKE ? OR description LIKE ?", trimmedSearch, trimmedSearch, trimmedSearch)
	}

	var total int64
	if err := query.Count(&total).Error; err != nil {
		return nil, 0, err
	}

	var items []localdb.LocalPartnumberBookItem
	if page > 0 && perPage > 0 {
		query = query.Offset((page - 1) * perPage).Limit(perPage)
	}
	err := query.Order("partnumber ASC, id ASC").Find(&items).Error
	return items, total, err
}
