package repository

import (
	"encoding/json"
	"fmt"
	"time"

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

// DataSource defines the unified interface for data access
// It abstracts whether data comes from MariaDB (online) or SQLite (offline)
type DataSource interface {
	// Components
	GetComponents(filter ComponentFilter, offset, limit int) ([]models.LotMetadata, int64, error)
	GetComponent(lotName string) (*models.LotMetadata, error)

	// Configurations
	SaveConfiguration(cfg *models.Configuration) error
	GetConfigurations(ownerUsername string) ([]models.Configuration, error)
	GetConfigurationByUUID(uuid string) (*models.Configuration, error)
	DeleteConfiguration(uuid string) error

	// Pricelists (read-only in offline mode)
	GetPricelists() ([]models.PricelistSummary, error)
	GetPricelistByID(id uint) (*models.Pricelist, error)
	GetPricelistItems(pricelistID uint) ([]models.PricelistItem, error)
	GetLatestPricelist() (*models.Pricelist, error)
}

// UnifiedRepo implements DataSource with automatic online/offline switching
type UnifiedRepo struct {
	mariaDB  *gorm.DB
	localDB  *localdb.LocalDB
	isOnline bool
}

// NewUnifiedRepo creates a new unified repository
func NewUnifiedRepo(mariaDB *gorm.DB, localDB *localdb.LocalDB, isOnline bool) *UnifiedRepo {
	return &UnifiedRepo{
		mariaDB:  mariaDB,
		localDB:  localDB,
		isOnline: isOnline,
	}
}

// SetOnlineStatus updates the online/offline status
func (r *UnifiedRepo) SetOnlineStatus(online bool) {
	r.isOnline = online
}

// IsOnline returns the current online/offline status
func (r *UnifiedRepo) IsOnline() bool {
	return r.isOnline
}

// Component methods

// GetComponents returns components from MariaDB (online) or local cache (offline)
func (r *UnifiedRepo) GetComponents(filter ComponentFilter, offset, limit int) ([]models.LotMetadata, int64, error) {
	if r.isOnline {
		return r.getComponentsOnline(filter, offset, limit)
	}
	return r.getComponentsOffline(filter, offset, limit)
}

func (r *UnifiedRepo) getComponentsOnline(filter ComponentFilter, offset, limit int) ([]models.LotMetadata, int64, error) {
	repo := NewComponentRepository(r.mariaDB)
	return repo.List(filter, offset, limit)
}

func (r *UnifiedRepo) getComponentsOffline(filter ComponentFilter, offset, limit int) ([]models.LotMetadata, int64, error) {
	var components []localdb.LocalComponent
	query := r.localDB.DB().Model(&localdb.LocalComponent{})

	// Apply filters
	if filter.Category != "" {
		query = query.Where("category = ?", filter.Category)
	}
	if filter.Search != "" {
		search := "%" + filter.Search + "%"
		query = query.Where("lot_name LIKE ? OR lot_description LIKE ? OR model LIKE ?", search, search, search)
	}
	var total int64
	query.Count(&total)

	// Apply sorting
	sortDir := "ASC"
	if filter.SortDir == "desc" {
		sortDir = "DESC"
	}
	switch filter.SortField {
	case "lot_name":
		query = query.Order("lot_name " + sortDir)
	default:
		query = query.Order("lot_name ASC")
	}

	if err := query.Offset(offset).Limit(limit).Find(&components).Error; err != nil {
		return nil, 0, fmt.Errorf("fetching offline components: %w", err)
	}

	// Convert to models.LotMetadata
	result := make([]models.LotMetadata, len(components))
	for i, comp := range components {
		result[i] = models.LotMetadata{
			LotName: comp.LotName,
			Model:   comp.Model,
			Lot: &models.Lot{
				LotName:        comp.LotName,
				LotDescription: comp.LotDescription,
			},
		}
	}

	return result, total, nil
}

// GetComponent returns a single component by lot name
func (r *UnifiedRepo) GetComponent(lotName string) (*models.LotMetadata, error) {
	if r.isOnline {
		repo := NewComponentRepository(r.mariaDB)
		return repo.GetByLotName(lotName)
	}

	var comp localdb.LocalComponent
	if err := r.localDB.DB().Where("lot_name = ?", lotName).First(&comp).Error; err != nil {
		return nil, fmt.Errorf("fetching offline component: %w", err)
	}

	return &models.LotMetadata{
		LotName: comp.LotName,
		Model:   comp.Model,
		Lot: &models.Lot{
			LotName:        comp.LotName,
			LotDescription: comp.LotDescription,
		},
	}, nil
}

// Configuration methods

// SaveConfiguration saves a configuration (online: MariaDB, offline: SQLite + pending_changes)
func (r *UnifiedRepo) SaveConfiguration(cfg *models.Configuration) error {
	if r.isOnline {
		repo := NewConfigurationRepository(r.mariaDB)
		return repo.Create(cfg)
	}

	// Offline: save to local SQLite and queue for sync
	localCfg := &localdb.LocalConfiguration{
		UUID:             cfg.UUID,
		Name:             cfg.Name,
		TotalPrice:       cfg.TotalPrice,
		CustomPrice:      cfg.CustomPrice,
		Notes:            cfg.Notes,
		IsTemplate:       cfg.IsTemplate,
		ServerCount:      cfg.ServerCount,
		CreatedAt:        cfg.CreatedAt,
		UpdatedAt:        time.Now(),
		SyncStatus:       "pending",
		OriginalUsername: cfg.OwnerUsername,
	}

	// Convert items
	localItems := make(localdb.LocalConfigItems, len(cfg.Items))
	for i, item := range cfg.Items {
		localItems[i] = localdb.LocalConfigItem{
			LotName:   item.LotName,
			Quantity:  item.Quantity,
			UnitPrice: item.UnitPrice,
		}
	}
	localCfg.Items = localItems

	if err := r.localDB.SaveConfiguration(localCfg); err != nil {
		return fmt.Errorf("saving local configuration: %w", err)
	}

	// Add to pending changes queue
	payload, err := json.Marshal(cfg)
	if err != nil {
		return fmt.Errorf("marshaling configuration for sync: %w", err)
	}

	return r.localDB.AddPendingChange("configuration", cfg.UUID, "create", string(payload))
}

// GetConfigurations returns all configurations for a user
func (r *UnifiedRepo) GetConfigurations(ownerUsername string) ([]models.Configuration, error) {
	if r.isOnline {
		repo := NewConfigurationRepository(r.mariaDB)
		configs, _, err := repo.ListByUser(ownerUsername, 0, 1000)
		return configs, err
	}

	// Offline: get from local SQLite
	localConfigs, err := r.localDB.GetConfigurations()
	if err != nil {
		return nil, fmt.Errorf("fetching local configurations: %w", err)
	}

	// Convert to models.Configuration
	result := make([]models.Configuration, len(localConfigs))
	for i, lc := range localConfigs {
		items := make(models.ConfigItems, len(lc.Items))
		for j, item := range lc.Items {
			items[j] = models.ConfigItem{
				LotName:   item.LotName,
				Quantity:  item.Quantity,
				UnitPrice: item.UnitPrice,
			}
		}

		result[i] = models.Configuration{
			UUID:          lc.UUID,
			OwnerUsername: lc.OriginalUsername,
			Name:          lc.Name,
			Items:         items,
			TotalPrice:    lc.TotalPrice,
			CustomPrice:   lc.CustomPrice,
			Notes:         lc.Notes,
			IsTemplate:    lc.IsTemplate,
			ServerCount:   lc.ServerCount,
			CreatedAt:     lc.CreatedAt,
		}
	}

	return result, nil
}

// GetConfigurationByUUID returns a configuration by UUID
func (r *UnifiedRepo) GetConfigurationByUUID(uuid string) (*models.Configuration, error) {
	if r.isOnline {
		repo := NewConfigurationRepository(r.mariaDB)
		return repo.GetByUUID(uuid)
	}

	localCfg, err := r.localDB.GetConfigurationByUUID(uuid)
	if err != nil {
		return nil, fmt.Errorf("fetching local configuration: %w", err)
	}

	items := make(models.ConfigItems, len(localCfg.Items))
	for i, item := range localCfg.Items {
		items[i] = models.ConfigItem{
			LotName:   item.LotName,
			Quantity:  item.Quantity,
			UnitPrice: item.UnitPrice,
		}
	}

	return &models.Configuration{
		UUID:        localCfg.UUID,
		Name:        localCfg.Name,
		Items:       items,
		TotalPrice:  localCfg.TotalPrice,
		CustomPrice: localCfg.CustomPrice,
		Notes:       localCfg.Notes,
		IsTemplate:  localCfg.IsTemplate,
		ServerCount: localCfg.ServerCount,
		CreatedAt:   localCfg.CreatedAt,
	}, nil
}

// DeleteConfiguration deletes a configuration
func (r *UnifiedRepo) DeleteConfiguration(uuid string) error {
	if r.isOnline {
		// Get ID first
		cfg, err := r.GetConfigurationByUUID(uuid)
		if err != nil {
			return err
		}
		repo := NewConfigurationRepository(r.mariaDB)
		return repo.Delete(cfg.ID)
	}

	// Offline: delete from local and queue sync
	if err := r.localDB.DeleteConfiguration(uuid); err != nil {
		return fmt.Errorf("deleting local configuration: %w", err)
	}

	return r.localDB.AddPendingChange("configuration", uuid, "delete", "")
}

// Pricelist methods

// GetPricelists returns all pricelists
func (r *UnifiedRepo) GetPricelists() ([]models.PricelistSummary, error) {
	if r.isOnline {
		repo := NewPricelistRepository(r.mariaDB)
		summaries, _, err := repo.List(0, 1000)
		return summaries, err
	}

	// Offline: get from local cache
	localPLs, err := r.localDB.GetLocalPricelists()
	if err != nil {
		return nil, fmt.Errorf("fetching local pricelists: %w", err)
	}

	summaries := make([]models.PricelistSummary, len(localPLs))
	for i, pl := range localPLs {
		itemCount := r.localDB.CountLocalPricelistItems(pl.ID)
		summaries[i] = models.PricelistSummary{
			ID:        pl.ServerID,
			Version:   pl.Version,
			CreatedAt: pl.CreatedAt,
			ItemCount: itemCount,
		}
	}

	return summaries, nil
}

// GetPricelistByID returns a pricelist by ID
func (r *UnifiedRepo) GetPricelistByID(id uint) (*models.Pricelist, error) {
	if r.isOnline {
		repo := NewPricelistRepository(r.mariaDB)
		return repo.GetByID(id)
	}

	// Offline: get from local cache
	localPL, err := r.localDB.GetLocalPricelistByServerID(id)
	if err != nil {
		return nil, fmt.Errorf("fetching local pricelist: %w", err)
	}

	itemCount := r.localDB.CountLocalPricelistItems(localPL.ID)
	return &models.Pricelist{
		ID:        localPL.ServerID,
		Version:   localPL.Version,
		CreatedAt: localPL.CreatedAt,
		ItemCount: int(itemCount),
	}, nil
}

// GetPricelistItems returns items for a pricelist
func (r *UnifiedRepo) GetPricelistItems(pricelistID uint) ([]models.PricelistItem, error) {
	if r.isOnline {
		repo := NewPricelistRepository(r.mariaDB)
		items, _, err := repo.GetItems(pricelistID, 0, 100000, "")
		return items, err
	}

	// Offline: get from local cache
	// First find the local pricelist by server ID
	localPL, err := r.localDB.GetLocalPricelistByServerID(pricelistID)
	if err != nil {
		return nil, fmt.Errorf("fetching local pricelist: %w", err)
	}

	localItems, err := r.localDB.GetLocalPricelistItems(localPL.ID)
	if err != nil {
		return nil, fmt.Errorf("fetching local pricelist items: %w", err)
	}

	items := make([]models.PricelistItem, len(localItems))
	for i, item := range localItems {
		items[i] = models.PricelistItem{
			ID:          item.ID,
			PricelistID: pricelistID,
			LotName:     item.LotName,
			Price:       item.Price,
		}
	}

	return items, nil
}

// GetLatestPricelist returns the latest pricelist
func (r *UnifiedRepo) GetLatestPricelist() (*models.Pricelist, error) {
	if r.isOnline {
		repo := NewPricelistRepository(r.mariaDB)
		return repo.GetLatestActive()
	}

	// Offline: get from local cache
	localPL, err := r.localDB.GetLatestLocalPricelist()
	if err != nil {
		return nil, fmt.Errorf("fetching latest local pricelist: %w", err)
	}

	itemCount := r.localDB.CountLocalPricelistItems(localPL.ID)
	return &models.Pricelist{
		ID:        localPL.ServerID,
		Version:   localPL.Version,
		CreatedAt: localPL.CreatedAt,
		ItemCount: int(itemCount),
	}, nil
}
