package commerce

import (
	"context"
	"errors"
	"log/slog"

	"gorm.io/gorm"

	"qycw/go-server/internal/domain/identity"
	"qycw/go-server/internal/plugins"
	redisstore "qycw/go-server/internal/redis"
)

type Service struct {
	db           *gorm.DB
	redis        *redisstore.Client
	logger       *slog.Logger
	plugins      *plugins.Service
	identity     *identity.Service
	taskExecutor TaskExecutor
}

func (s *Service) WithTaskExecutor(executor TaskExecutor) *Service {
	next := *s
	next.taskExecutor = executor
	return &next
}

func NewService(db *gorm.DB, redis *redisstore.Client, logger *slog.Logger, pluginSvc ...*plugins.Service) *Service {
	var pluginsService *plugins.Service
	if len(pluginSvc) > 0 {
		pluginsService = pluginSvc[0]
	}
	return &Service{db: db, redis: redis, logger: logger, plugins: pluginsService}
}

func (s *Service) WithIdentity(identityService *identity.Service) *Service {
	next := *s
	next.identity = identityService
	return &next
}

func (s *Service) withDB(db *gorm.DB) *Service {
	next := *s
	next.db = db
	return &next
}

func (s *Service) ListCategories(ctx context.Context) ([]CategoryResponse, error) {
	if err := s.requireDB(); err != nil {
		return nil, err
	}
	var categories []ProductCategory
	if err := s.db.WithContext(ctx).
		Where("status = ?", "active").
		Order("sort_order ASC, id ASC").
		Find(&categories).Error; err != nil {
		return nil, err
	}
	out := make([]CategoryResponse, 0, len(categories))
	for _, category := range categories {
		out = append(out, newCategoryResponse(category))
	}
	return out, nil
}

func (s *Service) ListGroups(ctx context.Context, categorySlug string) ([]GroupResponse, error) {
	if err := s.requireDB(); err != nil {
		return nil, err
	}
	category, err := s.activeCategoryBySlug(ctx, categorySlug)
	if err != nil {
		return nil, err
	}

	var groups []ProductGroup
	if err := s.db.WithContext(ctx).
		Where("category_id = ?", category.ID).
		Order("sort_order ASC, id ASC").
		Find(&groups).Error; err != nil {
		return nil, err
	}
	out := make([]GroupResponse, 0, len(groups))
	for _, group := range groups {
		out = append(out, newGroupResponse(group))
	}
	return out, nil
}

func (s *Service) ListProducts(ctx context.Context, categorySlug string, groupSlug string) ([]ProductResponse, error) {
	if err := s.requireDB(); err != nil {
		return nil, err
	}
	category, err := s.activeCategoryBySlug(ctx, categorySlug)
	if err != nil {
		return nil, err
	}
	group, err := s.groupBySlug(ctx, category.ID, groupSlug)
	if err != nil {
		return nil, err
	}

	var products []Product
	if err := s.db.WithContext(ctx).
		Where("group_id = ?", group.ID).
		Order("sort_order ASC, id DESC").
		Find(&products).Error; err != nil {
		return nil, err
	}
	if len(products) == 0 {
		return []ProductResponse{}, nil
	}

	productIDs := make([]int64, 0, len(products))
	for _, product := range products {
		productIDs = append(productIDs, product.ID)
	}
	var pricings []ProductPricing
	if err := s.db.WithContext(ctx).
		Where("product_id IN ?", productIDs).
		Order("product_id ASC, sort_order ASC").
		Find(&pricings).Error; err != nil {
		return nil, err
	}
	byProduct := map[int64][]ProductPricing{}
	for _, pricing := range pricings {
		byProduct[pricing.ProductID] = append(byProduct[pricing.ProductID], pricing)
	}

	out := make([]ProductResponse, 0, len(products))
	for _, product := range products {
		if product.Status != "active" {
			continue
		}
		if readinessCount(s.productReadinessIssues(ctx, product), "error") > 0 {
			continue
		}
		plugin := s.publicPluginForProduct(product)
		out = append(out, newProductResponse(product, byProduct[product.ID], plugin))
	}
	return out, nil
}

func (s *Service) activeCategoryBySlug(ctx context.Context, slug string) (ProductCategory, error) {
	var category ProductCategory
	err := s.db.WithContext(ctx).
		Where("slug = ?", slug).
		First(&category).Error
	if errors.Is(err, gorm.ErrRecordNotFound) {
		return ProductCategory{}, NotFound("分类不存在")
	}
	if err != nil {
		return ProductCategory{}, err
	}
	if category.Status != "active" {
		return ProductCategory{}, NotFound("分类不存在")
	}
	return category, nil
}

func (s *Service) groupBySlug(ctx context.Context, categoryID int64, slug string) (ProductGroup, error) {
	var group ProductGroup
	err := s.db.WithContext(ctx).
		Where("category_id = ? AND slug = ?", categoryID, slug).
		First(&group).Error
	if errors.Is(err, gorm.ErrRecordNotFound) {
		return ProductGroup{}, NotFound("产品组不存在")
	}
	if err != nil {
		return ProductGroup{}, err
	}
	return group, nil
}

func (s *Service) publicPluginForProduct(product Product) map[string]any {
	if product.APIType != "plugin" || product.APIIdentifier == nil || *product.APIIdentifier == "" {
		return nil
	}
	if s.plugins != nil {
		if manifest, err := s.plugins.Manifest(context.Background(), *product.APIIdentifier); err == nil && manifest.HasCapability(plugins.CapabilityProvision) {
			return publicGoPluginManifest(manifest)
		}
	}
	manifest, err := publicProvisionManifest(*product.APIIdentifier)
	if err != nil {
		if s.logger != nil {
			s.logger.Warn("provision plugin manifest unavailable", "plugin", *product.APIIdentifier, "error", err)
		}
		return nil
	}
	return manifest
}

func (s *Service) requireDB() error {
	if s.db == nil {
		return ServiceUnavailable(msgDatabaseDisabled)
	}
	return nil
}
