package repository

import (
	"errors"
	"fmt"
	"strconv"
	"strings"
	"time"

	"git.mchus.pro/mchus/quoteforge/internal/models"
	"gorm.io/gorm"
)

type PricelistRepository struct {
	db *gorm.DB
}

func NewPricelistRepository(db *gorm.DB) *PricelistRepository {
	return &PricelistRepository{db: db}
}

// List returns pricelists with pagination
func (r *PricelistRepository) List(offset, limit int) ([]models.PricelistSummary, int64, error) {
	return r.ListBySource("", offset, limit)
}

// ListBySource returns pricelists filtered by source when provided.
func (r *PricelistRepository) ListBySource(source string, offset, limit int) ([]models.PricelistSummary, int64, error) {
	query := r.db.Model(&models.Pricelist{}).
		Where("EXISTS (SELECT 1 FROM qt_pricelist_items WHERE qt_pricelist_items.pricelist_id = qt_pricelists.id)")
	if source != "" {
		query = query.Where("source = ?", source)
	}

	var total int64
	if err := query.Count(&total).Error; err != nil {
		return nil, 0, fmt.Errorf("counting pricelists: %w", err)
	}

	var pricelists []models.Pricelist
	if err := query.Order("created_at DESC, id DESC").Offset(offset).Limit(limit).Find(&pricelists).Error; err != nil {
		return nil, 0, fmt.Errorf("listing pricelists: %w", err)
	}

	return r.toSummaries(pricelists), total, nil
}

// ListActive returns active pricelists with pagination.
func (r *PricelistRepository) ListActive(offset, limit int) ([]models.PricelistSummary, int64, error) {
	return r.ListActiveBySource("", offset, limit)
}

// ListActiveBySource returns active pricelists filtered by source when provided.
func (r *PricelistRepository) ListActiveBySource(source string, offset, limit int) ([]models.PricelistSummary, int64, error) {
	query := r.db.Model(&models.Pricelist{}).
		Where("is_active = ?", true).
		Where("EXISTS (SELECT 1 FROM qt_pricelist_items WHERE qt_pricelist_items.pricelist_id = qt_pricelists.id)")
	if source != "" {
		query = query.Where("source = ?", source)
	}

	var total int64
	if err := query.Count(&total).Error; err != nil {
		return nil, 0, fmt.Errorf("counting active pricelists: %w", err)
	}

	var pricelists []models.Pricelist
	if err := query.Order("created_at DESC, id DESC").Offset(offset).Limit(limit).Find(&pricelists).Error; err != nil {
		return nil, 0, fmt.Errorf("listing active pricelists: %w", err)
	}

	return r.toSummaries(pricelists), total, nil
}

// CountActive returns the number of active pricelists.
func (r *PricelistRepository) CountActive() (int64, error) {
	var total int64
	if err := r.db.Model(&models.Pricelist{}).Where("is_active = ?", true).Count(&total).Error; err != nil {
		return 0, fmt.Errorf("counting active pricelists: %w", err)
	}
	return total, nil
}

func (r *PricelistRepository) toSummaries(pricelists []models.Pricelist) []models.PricelistSummary {
	// Get item counts for each pricelist
	summaries := make([]models.PricelistSummary, len(pricelists))
	for i, pl := range pricelists {
		var itemCount int64
		r.db.Model(&models.PricelistItem{}).Where("pricelist_id = ?", pl.ID).Count(&itemCount)
		usageCount, _ := r.CountUsage(pl.ID)

		summaries[i] = models.PricelistSummary{
			ID:           pl.ID,
			Source:       pl.Source,
			Version:      pl.Version,
			Notification: pl.Notification,
			CreatedAt:    pl.CreatedAt,
			CreatedBy:    pl.CreatedBy,
			IsActive:     pl.IsActive,
			UsageCount:   int(usageCount),
			ExpiresAt:    pl.ExpiresAt,
			ItemCount:    itemCount,
		}
	}

	return summaries
}

// GetByID returns a pricelist by ID
func (r *PricelistRepository) GetByID(id uint) (*models.Pricelist, error) {
	var pricelist models.Pricelist
	if err := r.db.First(&pricelist, id).Error; err != nil {
		return nil, fmt.Errorf("getting pricelist: %w", err)
	}

	// Get item count
	var itemCount int64
	r.db.Model(&models.PricelistItem{}).Where("pricelist_id = ?", id).Count(&itemCount)
	pricelist.ItemCount = int(itemCount)
	if usageCount, err := r.CountUsage(id); err == nil {
		pricelist.UsageCount = int(usageCount)
	}

	return &pricelist, nil
}

// GetByVersion returns a pricelist by version string
func (r *PricelistRepository) GetByVersion(version string) (*models.Pricelist, error) {
	return r.GetBySourceAndVersion(string(models.PricelistSourceEstimate), version)
}

// GetBySourceAndVersion returns a pricelist by source/version.
func (r *PricelistRepository) GetBySourceAndVersion(source, version string) (*models.Pricelist, error) {
	var pricelist models.Pricelist
	if err := r.db.Where("source = ? AND version = ?", source, version).First(&pricelist).Error; err != nil {
		return nil, fmt.Errorf("getting pricelist by version: %w", err)
	}
	return &pricelist, nil
}

// GetLatestActive returns the most recent active pricelist
func (r *PricelistRepository) GetLatestActive() (*models.Pricelist, error) {
	return r.GetLatestActiveBySource(string(models.PricelistSourceEstimate))
}

// GetLatestActiveBySource returns the most recent active pricelist by source.
func (r *PricelistRepository) GetLatestActiveBySource(source string) (*models.Pricelist, error) {
	var pricelist models.Pricelist
	if err := r.db.
		Where("is_active = ? AND source = ?", true, source).
		Where("EXISTS (SELECT 1 FROM qt_pricelist_items WHERE qt_pricelist_items.pricelist_id = qt_pricelists.id)").
		Order("created_at DESC, id DESC").
		First(&pricelist).Error; err != nil {
		return nil, fmt.Errorf("getting latest pricelist: %w", err)
	}
	return &pricelist, nil
}

// Create creates a new pricelist
func (r *PricelistRepository) Create(pricelist *models.Pricelist) error {
	if err := r.db.Create(pricelist).Error; err != nil {
		return fmt.Errorf("creating pricelist: %w", err)
	}
	return nil
}

// Update updates a pricelist
func (r *PricelistRepository) Update(pricelist *models.Pricelist) error {
	if err := r.db.Save(pricelist).Error; err != nil {
		return fmt.Errorf("updating pricelist: %w", err)
	}
	return nil
}

// Delete deletes a pricelist if usage_count is 0
func (r *PricelistRepository) Delete(id uint) error {
	usageCount, err := r.CountUsage(id)
	if err != nil {
		return err
	}

	if usageCount > 0 {
		return fmt.Errorf("cannot delete pricelist with usage_count > 0 (current: %d)", usageCount)
	}

	// Delete items first
	if err := r.db.Where("pricelist_id = ?", id).Delete(&models.PricelistItem{}).Error; err != nil {
		return fmt.Errorf("deleting pricelist items: %w", err)
	}

	// Delete pricelist
	if err := r.db.Delete(&models.Pricelist{}, id).Error; err != nil {
		return fmt.Errorf("deleting pricelist: %w", err)
	}

	return nil
}

// CreateItems batch inserts pricelist items
func (r *PricelistRepository) CreateItems(items []models.PricelistItem) error {
	if len(items) == 0 {
		return nil
	}

	// Use batch insert for better performance
	batchSize := 500
	for i := 0; i < len(items); i += batchSize {
		end := i + batchSize
		if end > len(items) {
			end = len(items)
		}
		if err := r.db.CreateInBatches(items[i:end], batchSize).Error; err != nil {
			return fmt.Errorf("batch inserting pricelist items: %w", err)
		}
	}
	return nil
}

// GetItems returns pricelist items with pagination
func (r *PricelistRepository) GetItems(pricelistID uint, offset, limit int, search string) ([]models.PricelistItem, int64, error) {
	var total int64
	query := r.db.Model(&models.PricelistItem{}).Where("pricelist_id = ?", pricelistID)

	if search != "" {
		query = query.Where("lot_name LIKE ?", "%"+search+"%")
	}

	if err := query.Count(&total).Error; err != nil {
		return nil, 0, fmt.Errorf("counting pricelist items: %w", err)
	}

	var items []models.PricelistItem
	if err := query.Order("lot_name").Offset(offset).Limit(limit).Find(&items).Error; err != nil {
		return nil, 0, fmt.Errorf("listing pricelist items: %w", err)
	}

	// Enrich with lot descriptions
	for i := range items {
		var lot models.Lot
		if err := r.db.Where("lot_name = ?", items[i].LotName).First(&lot).Error; err == nil {
			items[i].LotDescription = lot.LotDescription
		}
		items[i].Category = strings.TrimSpace(items[i].LotCategory)
	}

	return items, total, nil
}

// GetLotNames returns distinct lot names from pricelist items.
func (r *PricelistRepository) GetLotNames(pricelistID uint) ([]string, error) {
	var lotNames []string
	if err := r.db.Model(&models.PricelistItem{}).
		Where("pricelist_id = ?", pricelistID).
		Distinct("lot_name").
		Order("lot_name ASC").
		Pluck("lot_name", &lotNames).Error; err != nil {
		return nil, fmt.Errorf("listing pricelist lot names: %w", err)
	}
	return lotNames, nil
}

// GetPriceForLot returns item price for a lot within a pricelist.
func (r *PricelistRepository) GetPriceForLot(pricelistID uint, lotName string) (float64, error) {
	var item models.PricelistItem
	if err := r.db.Where("pricelist_id = ? AND lot_name = ?", pricelistID, lotName).First(&item).Error; err != nil {
		return 0, err
	}
	return item.Price, nil
}

// GetPricesForLots returns price map for given lots within a pricelist.
func (r *PricelistRepository) GetPricesForLots(pricelistID uint, lotNames []string) (map[string]float64, error) {
	result := make(map[string]float64, len(lotNames))
	if pricelistID == 0 || len(lotNames) == 0 {
		return result, nil
	}

	var rows []models.PricelistItem
	if err := r.db.Select("lot_name, price").
		Where("pricelist_id = ? AND lot_name IN ?", pricelistID, lotNames).
		Find(&rows).Error; err != nil {
		return nil, err
	}

	for _, row := range rows {
		if row.Price > 0 {
			result[row.LotName] = row.Price
		}
	}
	return result, nil
}

// SetActive toggles active flag on a pricelist.
func (r *PricelistRepository) SetActive(id uint, isActive bool) error {
	return r.db.Model(&models.Pricelist{}).Where("id = ?", id).Update("is_active", isActive).Error
}

// GenerateVersion generates a new version string in format YYYY-MM-DD-NNN
func (r *PricelistRepository) GenerateVersion() (string, error) {
	return r.GenerateVersionBySource(string(models.PricelistSourceEstimate))
}

// GenerateVersionBySource generates a new version string in format YYYY-MM-DD-NNN scoped by source.
func (r *PricelistRepository) GenerateVersionBySource(source string) (string, error) {
	today := time.Now().Format("2006-01-02")
	prefix := versionPrefixBySource(source)

	var last models.Pricelist
	err := r.db.Model(&models.Pricelist{}).
		Select("version").
		Where("source = ? AND version LIKE ?", source, prefix+"-"+today+"-%").
		Order("version DESC").
		Limit(1).
		Take(&last).Error
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return fmt.Sprintf("%s-%s-001", prefix, today), nil
		}
		return "", fmt.Errorf("loading latest today's pricelist version: %w", err)
	}

	parts := strings.Split(last.Version, "-")
	if len(parts) < 4 {
		return "", fmt.Errorf("invalid pricelist version format: %s", last.Version)
	}

	n, err := strconv.Atoi(parts[len(parts)-1])
	if err != nil {
		return "", fmt.Errorf("parsing pricelist sequence %q: %w", parts[len(parts)-1], err)
	}

	return fmt.Sprintf("%s-%s-%03d", prefix, today, n+1), nil
}

func versionPrefixBySource(source string) string {
	switch models.NormalizePricelistSource(source) {
	case models.PricelistSourceWarehouse:
		return "S"
	case models.PricelistSourceCompetitor:
		return "B"
	default:
		return "E"
	}
}

// GetPriceForLotBySource returns item price for a lot from latest active pricelist of source.
func (r *PricelistRepository) GetPriceForLotBySource(source, lotName string) (float64, uint, error) {
	latest, err := r.GetLatestActiveBySource(source)
	if err != nil {
		return 0, 0, err
	}
	price, err := r.GetPriceForLot(latest.ID, lotName)
	if err != nil {
		return 0, 0, err
	}
	return price, latest.ID, nil
}

// CanWrite checks if the current database user has INSERT permission on qt_pricelists
func (r *PricelistRepository) CanWrite() bool {
	canWrite, _ := r.CanWriteDebug()
	return canWrite
}

// CanWriteDebug checks write permission and returns debug info
// Uses raw SQL with explicit columns to avoid schema mismatch issues
func (r *PricelistRepository) CanWriteDebug() (bool, string) {
	// Check if table exists first
	var count int64
	if err := r.db.Table("qt_pricelists").Count(&count).Error; err != nil {
		return false, fmt.Sprintf("table check failed: %v", err)
	}

	// Use raw SQL with only essential columns that always exist
	// This avoids GORM model validation and schema mismatch issues
	tx := r.db.Begin()
	if tx.Error != nil {
		return false, fmt.Sprintf("begin tx failed: %v", tx.Error)
	}
	defer tx.Rollback() // Always rollback - this is just a permission test

	testVersion := fmt.Sprintf("test-%06d", time.Now().Unix()%1000000)

	// Raw SQL insert with only core columns
	err := tx.Exec(`
		INSERT INTO qt_pricelists (version, created_by, is_active)
		VALUES (?, 'system', 1)
	`, testVersion).Error

	if err != nil {
		// Check if it's a permission error vs other errors
		errStr := err.Error()
		if strings.Contains(errStr, "INSERT command denied") ||
			strings.Contains(errStr, "Access denied") {
			return false, "no write permission"
		}
		return false, fmt.Sprintf("insert failed: %v", err)
	}

	return true, "ok"
}

// IncrementUsageCount increments the usage count for a pricelist
func (r *PricelistRepository) IncrementUsageCount(id uint) error {
	return r.db.Model(&models.Pricelist{}).Where("id = ?", id).
		UpdateColumn("usage_count", gorm.Expr("usage_count + 1")).Error
}

// DecrementUsageCount decrements the usage count for a pricelist
func (r *PricelistRepository) DecrementUsageCount(id uint) error {
	return r.db.Model(&models.Pricelist{}).Where("id = ?", id).
		UpdateColumn("usage_count", gorm.Expr("GREATEST(usage_count - 1, 0)")).Error
}

// CountUsage returns number of configurations referencing pricelist.
func (r *PricelistRepository) CountUsage(id uint) (int64, error) {
	var count int64
	if err := r.db.Table("qt_configurations").Where("pricelist_id = ?", id).Count(&count).Error; err != nil {
		return 0, fmt.Errorf("counting configurations for pricelist %d: %w", id, err)
	}
	return count, nil
}

// GetExpiredUnused returns pricelists that are expired and unused
func (r *PricelistRepository) GetExpiredUnused() ([]models.Pricelist, error) {
	var pricelists []models.Pricelist
	if err := r.db.Where("expires_at < ? AND usage_count = 0", time.Now()).
		Find(&pricelists).Error; err != nil {
		return nil, fmt.Errorf("getting expired pricelists: %w", err)
	}
	return pricelists, nil
}
