package services

import (
	"errors"
	"time"

	"git.mchus.pro/mchus/quoteforge/internal/models"
	"git.mchus.pro/mchus/quoteforge/internal/repository"
	"github.com/google/uuid"
)

var (
	ErrConfigNotFound  = errors.New("configuration not found")
	ErrConfigForbidden = errors.New("access to configuration forbidden")
)

// ConfigurationGetter is an interface for services that can retrieve configurations
// Used by handlers to work with both ConfigurationService and LocalConfigurationService
type ConfigurationGetter interface {
	GetByUUID(uuid string, ownerUsername string) (*models.Configuration, error)
	GetByUUIDNoAuth(uuid string) (*models.Configuration, error)
}

type ConfigurationService struct {
	configRepo    *repository.ConfigurationRepository
	projectRepo   *repository.ProjectRepository
	componentRepo *repository.ComponentRepository
	pricelistRepo *repository.PricelistRepository
	quoteService  *QuoteService
}

func NewConfigurationService(
	configRepo *repository.ConfigurationRepository,
	projectRepo *repository.ProjectRepository,
	componentRepo *repository.ComponentRepository,
	pricelistRepo *repository.PricelistRepository,
	quoteService *QuoteService,
) *ConfigurationService {
	return &ConfigurationService{
		configRepo:    configRepo,
		projectRepo:   projectRepo,
		componentRepo: componentRepo,
		pricelistRepo: pricelistRepo,
		quoteService:  quoteService,
	}
}

type CreateConfigRequest struct {
	Name                  string             `json:"name"`
	Items                 models.ConfigItems `json:"items"`
	ProjectUUID           *string            `json:"project_uuid,omitempty"`
	CustomPrice           *float64           `json:"custom_price"`
	Notes                 string             `json:"notes"`
	IsTemplate            bool               `json:"is_template"`
	ServerCount           int                `json:"server_count"`
	ServerModel           string             `json:"server_model,omitempty"`
	SupportCode           string             `json:"support_code,omitempty"`
	Article               string             `json:"article,omitempty"`
	PricelistID           *uint              `json:"pricelist_id,omitempty"`
	WarehousePricelistID  *uint              `json:"warehouse_pricelist_id,omitempty"`
	CompetitorPricelistID *uint              `json:"competitor_pricelist_id,omitempty"`
	ConfigType            string             `json:"config_type,omitempty"` // "server" | "storage"
	DisablePriceRefresh   bool               `json:"disable_price_refresh"`
	OnlyInStock           bool               `json:"only_in_stock"`
}

type ArticlePreviewRequest struct {
	Items       models.ConfigItems `json:"items"`
	ServerModel string             `json:"server_model"`
	SupportCode string             `json:"support_code,omitempty"`
	PricelistID *uint              `json:"pricelist_id,omitempty"`
}

func (s *ConfigurationService) Create(ownerUsername string, req *CreateConfigRequest) (*models.Configuration, error) {
	projectUUID, err := s.resolveProjectUUID(ownerUsername, req.ProjectUUID)
	if err != nil {
		return nil, err
	}
	pricelistID, err := s.resolvePricelistID(req.PricelistID)
	if err != nil {
		return nil, err
	}

	total := req.Items.Total()

	// If server count is greater than 1, multiply the total by server count
	if req.ServerCount > 1 {
		total *= float64(req.ServerCount)
	}

	config := &models.Configuration{
		UUID:                  uuid.New().String(),
		OwnerUsername:         ownerUsername,
		ProjectUUID:           projectUUID,
		Name:                  req.Name,
		Items:                 req.Items,
		TotalPrice:            &total,
		CustomPrice:           req.CustomPrice,
		Notes:                 req.Notes,
		IsTemplate:            req.IsTemplate,
		ServerCount:           req.ServerCount,
		ServerModel:           req.ServerModel,
		SupportCode:           req.SupportCode,
		Article:               req.Article,
		PricelistID:           pricelistID,
		WarehousePricelistID:  req.WarehousePricelistID,
		CompetitorPricelistID: req.CompetitorPricelistID,
		ConfigType:            req.ConfigType,
		DisablePriceRefresh:   req.DisablePriceRefresh,
		OnlyInStock:           req.OnlyInStock,
	}
	if config.ConfigType == "" {
		config.ConfigType = "server"
	}

	if err := s.configRepo.Create(config); err != nil {
		return nil, err
	}

	return config, nil
}

func (s *ConfigurationService) GetByUUID(uuid string, ownerUsername string) (*models.Configuration, error) {
	config, err := s.configRepo.GetByUUID(uuid)
	if err != nil {
		return nil, ErrConfigNotFound
	}

	// Allow access if user owns config or it's a template
	if !s.isOwner(config, ownerUsername) && !config.IsTemplate {
		return nil, ErrConfigForbidden
	}

	return config, nil
}

func (s *ConfigurationService) Update(uuid string, ownerUsername string, req *CreateConfigRequest) (*models.Configuration, error) {
	config, err := s.configRepo.GetByUUID(uuid)
	if err != nil {
		return nil, ErrConfigNotFound
	}

	if !s.isOwner(config, ownerUsername) {
		return nil, ErrConfigForbidden
	}

	projectUUID, err := s.resolveProjectUUID(ownerUsername, req.ProjectUUID)
	if err != nil {
		return nil, err
	}
	pricelistID, err := s.resolvePricelistID(req.PricelistID)
	if err != nil {
		return nil, err
	}

	total := req.Items.Total()

	// If server count is greater than 1, multiply the total by server count
	if req.ServerCount > 1 {
		total *= float64(req.ServerCount)
	}

	config.Name = req.Name
	config.ProjectUUID = projectUUID
	config.Items = req.Items
	config.TotalPrice = &total
	config.CustomPrice = req.CustomPrice
	config.Notes = req.Notes
	config.IsTemplate = req.IsTemplate
	config.ServerCount = req.ServerCount
	config.ServerModel = req.ServerModel
	config.SupportCode = req.SupportCode
	config.Article = req.Article
	config.PricelistID = pricelistID
	config.WarehousePricelistID = req.WarehousePricelistID
	config.CompetitorPricelistID = req.CompetitorPricelistID
	config.DisablePriceRefresh = req.DisablePriceRefresh
	config.OnlyInStock = req.OnlyInStock

	if err := s.configRepo.Update(config); err != nil {
		return nil, err
	}

	return config, nil
}

func (s *ConfigurationService) Delete(uuid string, ownerUsername string) error {
	config, err := s.configRepo.GetByUUID(uuid)
	if err != nil {
		return ErrConfigNotFound
	}

	if !s.isOwner(config, ownerUsername) {
		return ErrConfigForbidden
	}

	return s.configRepo.Delete(config.ID)
}

func (s *ConfigurationService) Rename(uuid string, ownerUsername string, newName string) (*models.Configuration, error) {
	config, err := s.configRepo.GetByUUID(uuid)
	if err != nil {
		return nil, ErrConfigNotFound
	}

	if !s.isOwner(config, ownerUsername) {
		return nil, ErrConfigForbidden
	}

	config.Name = newName

	if err := s.configRepo.Update(config); err != nil {
		return nil, err
	}

	return config, nil
}

func (s *ConfigurationService) Clone(configUUID string, ownerUsername string, newName string) (*models.Configuration, error) {
	return s.CloneToProject(configUUID, ownerUsername, newName, nil)
}

func (s *ConfigurationService) CloneToProject(configUUID string, ownerUsername string, newName string, projectUUID *string) (*models.Configuration, error) {
	original, err := s.GetByUUID(configUUID, ownerUsername)
	if err != nil {
		return nil, err
	}
	resolvedProjectUUID := original.ProjectUUID
	if projectUUID != nil {
		resolvedProjectUUID, err = s.resolveProjectUUID(ownerUsername, projectUUID)
		if err != nil {
			return nil, err
		}
	}

	// Create copy with new UUID and name
	total := original.Items.Total()

	// If server count is greater than 1, multiply the total by server count
	if original.ServerCount > 1 {
		total *= float64(original.ServerCount)
	}

	clone := &models.Configuration{
		UUID:                  uuid.New().String(),
		OwnerUsername:         ownerUsername,
		ProjectUUID:           resolvedProjectUUID,
		Name:                  newName,
		Items:                 original.Items,
		TotalPrice:            &total,
		CustomPrice:           original.CustomPrice,
		Notes:                 original.Notes,
		IsTemplate:            false, // Clone is never a template
		ServerCount:           original.ServerCount,
		ServerModel:           original.ServerModel,
		SupportCode:           original.SupportCode,
		Article:               original.Article,
		PricelistID:           original.PricelistID,
		WarehousePricelistID:  original.WarehousePricelistID,
		CompetitorPricelistID: original.CompetitorPricelistID,
		DisablePriceRefresh:   original.DisablePriceRefresh,
		OnlyInStock:           original.OnlyInStock,
	}

	if err := s.configRepo.Create(clone); err != nil {
		return nil, err
	}

	return clone, nil
}

func (s *ConfigurationService) ListByUser(ownerUsername string, page, perPage int) ([]models.Configuration, int64, error) {
	if page < 1 {
		page = 1
	}
	if perPage < 1 || perPage > 100 {
		perPage = 20
	}
	offset := (page - 1) * perPage

	return s.configRepo.ListByUser(ownerUsername, offset, perPage)
}

// ListAll returns all configurations without user filter (for use when auth is disabled)
func (s *ConfigurationService) ListAll(page, perPage int) ([]models.Configuration, int64, error) {
	if page < 1 {
		page = 1
	}
	if perPage < 1 || perPage > 100 {
		perPage = 20
	}
	offset := (page - 1) * perPage

	return s.configRepo.ListAll(offset, perPage)
}

// GetByUUIDNoAuth returns configuration without ownership check (for use when auth is disabled)
func (s *ConfigurationService) GetByUUIDNoAuth(uuid string) (*models.Configuration, error) {
	config, err := s.configRepo.GetByUUID(uuid)
	if err != nil {
		return nil, ErrConfigNotFound
	}
	return config, nil
}

// UpdateNoAuth updates configuration without ownership check
func (s *ConfigurationService) UpdateNoAuth(uuid string, req *CreateConfigRequest) (*models.Configuration, error) {
	config, err := s.configRepo.GetByUUID(uuid)
	if err != nil {
		return nil, ErrConfigNotFound
	}

	projectUUID, err := s.resolveProjectUUID(config.OwnerUsername, req.ProjectUUID)
	if err != nil {
		return nil, err
	}
	pricelistID, err := s.resolvePricelistID(req.PricelistID)
	if err != nil {
		return nil, err
	}

	total := req.Items.Total()
	if req.ServerCount > 1 {
		total *= float64(req.ServerCount)
	}

	config.Name = req.Name
	config.ProjectUUID = projectUUID
	config.Items = req.Items
	config.TotalPrice = &total
	config.CustomPrice = req.CustomPrice
	config.Notes = req.Notes
	config.IsTemplate = req.IsTemplate
	config.ServerCount = req.ServerCount
	config.ServerModel = req.ServerModel
	config.SupportCode = req.SupportCode
	config.Article = req.Article
	config.PricelistID = pricelistID
	config.WarehousePricelistID = req.WarehousePricelistID
	config.CompetitorPricelistID = req.CompetitorPricelistID
	config.DisablePriceRefresh = req.DisablePriceRefresh
	config.OnlyInStock = req.OnlyInStock

	if err := s.configRepo.Update(config); err != nil {
		return nil, err
	}

	return config, nil
}

// DeleteNoAuth deletes configuration without ownership check
func (s *ConfigurationService) DeleteNoAuth(uuid string) error {
	config, err := s.configRepo.GetByUUID(uuid)
	if err != nil {
		return ErrConfigNotFound
	}
	return s.configRepo.Delete(config.ID)
}

// RenameNoAuth renames configuration without ownership check
func (s *ConfigurationService) RenameNoAuth(uuid string, newName string) (*models.Configuration, error) {
	config, err := s.configRepo.GetByUUID(uuid)
	if err != nil {
		return nil, ErrConfigNotFound
	}

	config.Name = newName
	if err := s.configRepo.Update(config); err != nil {
		return nil, err
	}

	return config, nil
}

// CloneNoAuth clones configuration without ownership check
func (s *ConfigurationService) CloneNoAuth(configUUID string, newName string, ownerUsername string) (*models.Configuration, error) {
	return s.CloneNoAuthToProject(configUUID, newName, ownerUsername, nil)
}

func (s *ConfigurationService) CloneNoAuthToProject(configUUID string, newName string, ownerUsername string, projectUUID *string) (*models.Configuration, error) {
	original, err := s.configRepo.GetByUUID(configUUID)
	if err != nil {
		return nil, ErrConfigNotFound
	}
	resolvedProjectUUID := original.ProjectUUID
	if projectUUID != nil {
		resolvedProjectUUID, err = s.resolveProjectUUID(ownerUsername, projectUUID)
		if err != nil {
			return nil, err
		}
	}

	total := original.Items.Total()
	if original.ServerCount > 1 {
		total *= float64(original.ServerCount)
	}

	clone := &models.Configuration{
		UUID:          uuid.New().String(),
		OwnerUsername: ownerUsername,
		ProjectUUID:   resolvedProjectUUID,
		Name:          newName,
		Items:         original.Items,
		TotalPrice:    &total,
		CustomPrice:   original.CustomPrice,
		Notes:         original.Notes,
		IsTemplate:    false,
		ServerCount:   original.ServerCount,
		PricelistID:   original.PricelistID,
		OnlyInStock:   original.OnlyInStock,
	}

	if err := s.configRepo.Create(clone); err != nil {
		return nil, err
	}

	return clone, nil
}

func (s *ConfigurationService) resolveProjectUUID(ownerUsername string, projectUUID *string) (*string, error) {
	_ = ownerUsername
	if s.projectRepo == nil {
		return projectUUID, nil
	}
	if projectUUID == nil || *projectUUID == "" {
		return nil, nil
	}

	project, err := s.projectRepo.GetByUUID(*projectUUID)
	if err != nil {
		return nil, ErrProjectNotFound
	}
	if !project.IsActive {
		return nil, errors.New("project is archived")
	}

	return &project.UUID, nil
}

func (s *ConfigurationService) resolvePricelistID(pricelistID *uint) (*uint, error) {
	if s.pricelistRepo == nil {
		return pricelistID, nil
	}
	if pricelistID != nil && *pricelistID > 0 {
		if _, err := s.pricelistRepo.GetByID(*pricelistID); err != nil {
			return nil, err
		}
		return pricelistID, nil
	}
	latest, err := s.pricelistRepo.GetLatestActive()
	if err != nil {
		return nil, nil
	}
	return &latest.ID, nil
}

// RefreshPricesNoAuth refreshes prices without ownership check
func (s *ConfigurationService) RefreshPricesNoAuth(uuid string) (*models.Configuration, error) {
	config, err := s.configRepo.GetByUUID(uuid)
	if err != nil {
		return nil, ErrConfigNotFound
	}

	var latestPricelistID *uint
	if s.pricelistRepo != nil {
		if pl, err := s.pricelistRepo.GetLatestActive(); err == nil {
			latestPricelistID = &pl.ID
		}
	}

	updatedItems := make(models.ConfigItems, len(config.Items))
	for i, item := range config.Items {
		if latestPricelistID != nil {
			if price, err := s.pricelistRepo.GetPriceForLot(*latestPricelistID, item.LotName); err == nil && price > 0 {
				updatedItems[i] = models.ConfigItem{
					LotName:   item.LotName,
					Quantity:  item.Quantity,
					UnitPrice: price,
				}
				continue
			}
		}

		if s.componentRepo == nil {
			updatedItems[i] = item
			continue
		}
		metadata, err := s.componentRepo.GetByLotName(item.LotName)
		if err != nil || metadata.CurrentPrice == nil {
			updatedItems[i] = item
			continue
		}

		updatedItems[i] = models.ConfigItem{
			LotName:   item.LotName,
			Quantity:  item.Quantity,
			UnitPrice: *metadata.CurrentPrice,
		}
	}

	config.Items = updatedItems
	total := updatedItems.Total()
	if config.ServerCount > 1 {
		total *= float64(config.ServerCount)
	}

	config.TotalPrice = &total
	if latestPricelistID != nil {
		config.PricelistID = latestPricelistID
	}
	now := time.Now()
	config.PriceUpdatedAt = &now

	if err := s.configRepo.Update(config); err != nil {
		return nil, err
	}

	return config, nil
}

func (s *ConfigurationService) ListTemplates(page, perPage int) ([]models.Configuration, int64, error) {
	if page < 1 {
		page = 1
	}
	if perPage < 1 || perPage > 100 {
		perPage = 20
	}
	offset := (page - 1) * perPage

	return s.configRepo.ListTemplates(offset, perPage)
}

// RefreshPrices updates all component prices in the configuration with current prices
func (s *ConfigurationService) RefreshPrices(uuid string, ownerUsername string) (*models.Configuration, error) {
	config, err := s.configRepo.GetByUUID(uuid)
	if err != nil {
		return nil, ErrConfigNotFound
	}

	if !s.isOwner(config, ownerUsername) {
		return nil, ErrConfigForbidden
	}

	var latestPricelistID *uint
	if s.pricelistRepo != nil {
		if pl, err := s.pricelistRepo.GetLatestActive(); err == nil {
			latestPricelistID = &pl.ID
		}
	}

	// Update prices for all items
	updatedItems := make(models.ConfigItems, len(config.Items))
	for i, item := range config.Items {
		if latestPricelistID != nil {
			if price, err := s.pricelistRepo.GetPriceForLot(*latestPricelistID, item.LotName); err == nil && price > 0 {
				updatedItems[i] = models.ConfigItem{
					LotName:   item.LotName,
					Quantity:  item.Quantity,
					UnitPrice: price,
				}
				continue
			}
		}

		// Get current component price
		if s.componentRepo == nil {
			updatedItems[i] = item
			continue
		}
		metadata, err := s.componentRepo.GetByLotName(item.LotName)
		if err != nil || metadata.CurrentPrice == nil {
			// Keep original item if component not found or no price available
			updatedItems[i] = item
			continue
		}

		// Update item with current price
		updatedItems[i] = models.ConfigItem{
			LotName:   item.LotName,
			Quantity:  item.Quantity,
			UnitPrice: *metadata.CurrentPrice,
		}
	}

	// Update configuration
	config.Items = updatedItems
	total := updatedItems.Total()

	// If server count is greater than 1, multiply the total by server count
	if config.ServerCount > 1 {
		total *= float64(config.ServerCount)
	}

	config.TotalPrice = &total
	if latestPricelistID != nil {
		config.PricelistID = latestPricelistID
	}

	// Set price update timestamp
	now := time.Now()
	config.PriceUpdatedAt = &now

	if err := s.configRepo.Update(config); err != nil {
		return nil, err
	}

	return config, nil
}

func (s *ConfigurationService) isOwner(config *models.Configuration, ownerUsername string) bool {
	if config == nil || ownerUsername == "" {
		return false
	}
	return config.OwnerUsername == ownerUsername
}

// // Export configuration as JSON
// type ConfigExport struct {
// 	Name  string             `json:"name"`
// 	Notes string             `json:"notes"`
// 	Items models.ConfigItems `json:"items"`
// }
//
// func (s *ConfigurationService) ExportJSON(uuid string, userID uint) ([]byte, error) {
// 	config, err := s.GetByUUID(uuid, userID)
// 	if err != nil {
// 		return nil, err
// 	}
//
// 	export := ConfigExport{
// 		Name:  config.Name,
// 		Notes: config.Notes,
// 		Items: config.Items,
// 	}
//
// 	return json.MarshalIndent(export, "", "  ")
// }
//
// func (s *ConfigurationService) ImportJSON(userID uint, data []byte) (*models.Configuration, error) {
// 	var export ConfigExport
// 	if err := json.Unmarshal(data, &export); err != nil {
// 		return nil, err
// 	}
//
// 	req := &CreateConfigRequest{
// 		Name:  export.Name,
// 		Notes: export.Notes,
// 		Items: export.Items,
// 	}
//
// 	return s.Create(userID, req)
// }
