package repository

import (
	"fmt"
	"gorm.io/gorm"
	"log"
	"math"
	"rrbec_server/internal/models"
	"time"
)

type PaginationParams struct {
	Page    int
	Limit   int
	Sort    string
	Filters map[string]interface{}
}

type PaginatedResponse struct {
	Data       interface{} `json:"data"`
	Total      int64       `json:"total"`
	Page       int         `json:"page"`
	Limit      int         `json:"limit"`
	TotalPages int         `json:"total_pages"`
}

func (p *PaginationParams) Apply(db *gorm.DB, model interface{}) (*gorm.DB, int64, error) {
	if p.Page <= 0 {
		p.Page = 1
	}
	if p.Limit <= 0 {
		p.Limit = 50
	}
	if p.Limit > 200 {
		p.Limit = 200
	}

	query := db.Model(model)

	for key, value := range p.Filters {
		if key == "status" {
			if s, ok := value.(string); ok && s != "" {
				query = query.Where("status = ?", s)
			}
		} else if key == "active" {
			if b, ok := value.(bool); ok {
				query = query.Where("active = ?", b)
			}
		} else if key == "category" {
			if c, ok := value.(uint); ok && c > 0 {
				query = query.Where("category = ?", c)
			}
		} else if key == "cuisine" {
			if b, ok := value.(bool); ok {
				query = query.Where("cuisine = ?", b)
			}
		} else if key == "date_from" {
			if t, ok := value.(time.Time); ok {
				query = query.Where("date_time >= ? OR dt_open >= ? OR created_at >= ?", t, t, t)
			}
		} else if key == "date_to" {
			if t, ok := value.(time.Time); ok {
				query = query.Where("date_time <= ? OR dt_open <= ? OR created_at <= ?", t, t, t)
			}
		} else if key == "comanda_id" {
			if c, ok := value.(uint); ok && c > 0 {
				query = query.Where("comanda_id = ?", c)
			}
		} else if key == "name" {
			if n, ok := value.(string); ok && n != "" {
				query = query.Where("name LIKE ?", "%"+n+"%")
			}
		} else if key == "canceled" {
			if v, ok := value.(bool); ok {
				if v {
					query = query.Where("canceled IS NOT NULL")
				} else {
					query = query.Where("canceled IS NULL")
				}
			}
		} else if key == "delivered" {
			if v, ok := value.(bool); ok {
				if v {
					query = query.Where("delivered IS NOT NULL")
				} else {
					query = query.Where("delivered IS NULL")
				}
			}
		}
	}

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

	sort := "id ASC"
	if p.Sort != "" {
		sort = p.Sort
	}

	offset := (p.Page - 1) * p.Limit
	query = query.Order(sort).Offset(offset).Limit(p.Limit)

	return query, total, nil
}

func (p *PaginationParams) Paginate(db *gorm.DB, model interface{}, result interface{}) (*PaginatedResponse, error) {
	query, total, err := p.Apply(db, model)
	if err != nil {
		return nil, err
	}

	if err := query.Find(result).Error; err != nil {
		return nil, err
	}

	totalPages := int(math.Ceil(float64(total) / float64(p.Limit)))
	if totalPages == 0 {
		totalPages = 1
	}

	return &PaginatedResponse{
		Data:       result,
		Total:      total,
		Page:       p.Page,
		Limit:      p.Limit,
		TotalPages: totalPages,
	}, nil
}

type Repository struct {
	db *gorm.DB
}

func NewRepository(db *gorm.DB) *Repository {
	return &Repository{db: db}
}

// Mesa
func (r *Repository) GetMesas(params *PaginationParams) (*PaginatedResponse, error) {
	var mesas []models.Mesa
	return params.Paginate(r.db, &models.Mesa{}, &mesas)
}

func (r *Repository) CreateMesa(mesa *models.Mesa) error {
	return r.db.Create(mesa).Error
}

func (r *Repository) SaveMesa(mesa *models.Mesa) error {
	return r.db.Save(mesa).Error
}

// Client
func (r *Repository) GetClients(params *PaginationParams) (*PaginatedResponse, error) {
	var clients []models.Client
	return params.Paginate(r.db, &models.Client{}, &clients)
}

func (r *Repository) CreateClient(client *models.Client) error {
	return r.db.Create(client).Error
}

func (r *Repository) SaveClient(client *models.Client) error {
	return r.db.Save(client).Error
}

// Product
func (r *Repository) GetProducts(params *PaginationParams) (*PaginatedResponse, error) {
	var products []models.Product
	return params.Paginate(r.db, &models.Product{}, &products)
}

func (r *Repository) GetProductsAll(products *[]models.Product) error {
	return r.db.Find(products).Error
}

func (r *Repository) SaveProduct(product *models.Product) error {
	return r.db.Save(product).Error
}

func (r *Repository) GetProductByID(id uint) (*models.Product, error) {
	var product models.Product
	err := r.db.First(&product, id).Error
	return &product, err
}

func (r *Repository) UpdateProductFields(id uint, updates map[string]interface{}) error {
	updates["sincronizado"] = false
	return r.db.Model(&models.Product{}).Where("id = ?", id).Updates(updates).Error
}

func (r *Repository) DeductStock(productID uint, quantity int) error {
	return r.db.Model(&models.Product{}).Where("id = ?", productID).
		Update("quantity", gorm.Expr("quantity - ?", quantity)).Error
}

func (r *Repository) RestoreStock(productID uint, quantity int) error {
	return r.db.Model(&models.Product{}).Where("id = ?", productID).
		Update("quantity", gorm.Expr("quantity + ?", quantity)).Error
}

func (r *Repository) GetProductComponents(productID uint) ([]models.ProductComponent, error) {
	var components []models.ProductComponent
	err := r.db.Where("composite_product = ?", productID).Find(&components).Error
	return components, err
}

func (r *Repository) CreateProduct(product *models.Product) error {
	return r.db.Create(product).Error
}

// Comanda
func (r *Repository) GetComandas(params *PaginationParams) (*PaginatedResponse, error) {
	var comandas []models.Comanda
	if params == nil {
		params = &PaginationParams{Page: 1, Limit: 50}
	}

	query, total, err := params.Apply(r.db, &models.Comanda{})
	if err != nil {
		return nil, err
	}

	if err := query.Preload("Items").Find(&comandas).Error; err != nil {
		return nil, err
	}

	totalPages := int(math.Ceil(float64(total) / float64(params.Limit)))
	if totalPages == 0 {
		totalPages = 1
	}

	return &PaginatedResponse{
		Data:       comandas,
		Total:      total,
		Page:       params.Page,
		Limit:      params.Limit,
		TotalPages: totalPages,
	}, nil
}

func (r *Repository) CreateComanda(comanda *models.Comanda) error {
	return r.db.Create(comanda).Error
}

func (r *Repository) SaveComanda(comanda *models.Comanda) error {
	return r.db.Save(comanda).Error
}

func (r *Repository) SaveCategory(cat *models.Category) error {
	return r.db.Save(cat).Error
}

func (r *Repository) CreateCategory(cat *models.Category) error {
	return r.db.Create(cat).Error
}

func (r *Repository) UpdateCategoryFields(id uint, updates map[string]interface{}) error {
	return r.db.Model(&models.Category{}).Where("id = ?", id).Updates(updates).Error
}

func (r *Repository) SaveTypePay(tp *models.TypePay) error {
	return r.db.Save(tp).Error
}

func (r *Repository) GetCategories(params *PaginationParams) (*PaginatedResponse, error) {
	var categories []models.Category
	return params.Paginate(r.db, &models.Category{}, &categories)
}

func (r *Repository) GetTypePayments(params *PaginationParams) (*PaginatedResponse, error) {
	var types []models.TypePay
	return params.Paginate(r.db, &models.TypePay{}, &types)
}

func (r *Repository) GetComandaByID(id uint) (*models.Comanda, error) {
	var comanda models.Comanda
	if err := r.db.First(&comanda, id).Error; err != nil {
		return nil, err
	}

	var items []models.ProductComanda
	r.db.Where("comanda_id = ?", comanda.ID).Find(&items)
	comanda.Items = items

	return &comanda, nil
}

// Items
func (r *Repository) AddItemToComanda(item *models.ProductComanda) error {
	return r.db.Create(item).Error
}

func (r *Repository) SaveProductComanda(item *models.ProductComanda) error {
	return r.db.Save(item).Error
}

func (r *Repository) GetItemsByComanda(comandaID uint) ([]models.ProductComanda, error) {
	var items []models.ProductComanda
	err := r.db.Where("comanda_id = ?", comandaID).Find(&items).Error
	return items, err
}

func (r *Repository) DeleteItem(itemID uint) error {
	return r.db.Delete(&models.ProductComanda{}, itemID).Error
}

func (r *Repository) GetOrders(params *PaginationParams) (*PaginatedResponse, error) {
	var orders []models.Order
	return params.Paginate(r.db, &models.Order{}, &orders)
}

func (r *Repository) SaveOrder(order *models.Order) error {
	return r.db.Save(order).Error
}

func (r *Repository) CreateOrder(order *models.Order) error {
	return r.db.Create(order).Error
}

func (r *Repository) GetOrderByPC(pcID uint) (*models.Order, error) {
	var order models.Order
	err := r.db.Where("product_comanda_id = ?", pcID).First(&order).Error
	return &order, err
}

func (r *Repository) UpdateOrder(order *models.Order) error {
	return r.db.Save(order).Error
}

func (r *Repository) UpdateOrderFields(id uint, updates map[string]interface{}) error {
	mappedUpdates := make(map[string]interface{})
	for k, v := range updates {
		switch k {
		case "productComanda":
			mappedUpdates["product_comanda_id"] = v
		case "id_product":
			mappedUpdates["product_id"] = v
		case "id_comanda":
			mappedUpdates["comanda_id"] = v
		default:
			mappedUpdates[k] = v
		}
	}

	mappedUpdates["sincronizado"] = false

	return r.db.Model(&models.Order{}).Where("id = ?", id).Updates(mappedUpdates).Error
}

func (r *Repository) GetPayments(params *PaginationParams) (*PaginatedResponse, error) {
	var payments []models.Payment
	return params.Paginate(r.db, &models.Payment{}, &payments)
}

func (r *Repository) CreatePayment(payment *models.Payment) error {
	return r.db.Create(payment).Error
}

func (r *Repository) SavePayment(payment *models.Payment) error {
	return r.db.Save(payment).Error
}

// Sync
func (r *Repository) GetUnsynced() (map[string][]interface{}, error) {
	// This is a simplified helper to find anything not synced.
	// In a real app, you'd iterate per table.
	return nil, nil // Placeholder for sync worker logic
}

func (r *Repository) MarkAsSynced(model interface{}, id uint) error {
	return r.db.Model(model).Where("id = ?", id).Update("sincronizado", true).Error
}

func (r *Repository) SafeChangeID(tableName string, oldID, newID uint) error {
	if oldID == newID {
		return nil
	}

	var count int64
	r.db.Table(tableName).Where("id = ?", newID).Count(&count)
	if count > 0 {
		squatterID := newID + 1000000
		for {
			var sCount int64
			r.db.Table(tableName).Where("id = ?", squatterID).Count(&sCount)
			if sCount == 0 {
				break
			}
			squatterID++
		}

		log.Printf("COLLISION: ID %d in %s taken! Moving squatter to %d", newID, tableName, squatterID)
		r.db.Exec(fmt.Sprintf("UPDATE %s SET id = ? WHERE id = ?", tableName), squatterID, newID)

		if tableName == "comandas" {
			r.UpdateFK("product_comandas", "comanda_id", newID, squatterID)
			r.UpdateFK("orders", "id_comanda", newID, squatterID)
			r.UpdateFK("payments", "comanda_id", newID, squatterID)
		} else if tableName == "product_comandas" {
			r.UpdateFK("orders", "product_comanda_id", newID, squatterID)
		}
	}

	return r.UpdateID(tableName, oldID, newID)
}

func (r *Repository) UpdateID(tableName string, oldID, newID uint) error {
	return r.db.Exec(fmt.Sprintf("UPDATE %s SET id = ? WHERE id = ?", tableName), newID, oldID).Error
}

func (r *Repository) UpdateFK(tableName, fkColumn string, oldID, newID uint) error {
	return r.db.Exec(fmt.Sprintf("UPDATE %s SET %s = ? WHERE %s = ?", tableName, fkColumn, fkColumn), newID, oldID).Error
}

// User
func (r *Repository) GetUserByUsername(username string) (*models.User, error) {
	var user models.User
	err := r.db.Where("username = ?", username).First(&user).Error
	return &user, err
}

func (r *Repository) SaveUser(user *models.User) error {
	return r.db.Save(user).Error
}

func (r *Repository) UpdateComandaStatus(id uint, status string) error {
	return r.db.Model(&models.Comanda{}).Where("id = ?", id).Updates(map[string]interface{}{"status": status, "sincronizado": false}).Error
}

func (r *Repository) ClearComandaItems(comandaID uint) error {
	return r.db.Where("comanda_id = ?", comandaID).Delete(&models.ProductComanda{}).Error
}

func (r *Repository) GetLastSyncID() int {
	var state models.SyncState
	r.db.FirstOrCreate(&state)
	return state.LastSyncID
}

func (r *Repository) SaveLastSyncID(id int) {
	r.db.Model(&models.SyncState{}).Where("id = ?", 1).Update("last_sync_id", id)
}

func (r *Repository) GetUnsyncedComandas() ([]models.Comanda, error) {
	var results []models.Comanda
	err := r.db.Where("sincronizado = ?", false).Find(&results).Error
	return results, err
}

func (r *Repository) GetUnsyncedItems() ([]models.ProductComanda, error) {
	var results []models.ProductComanda
	err := r.db.Where("sincronizado = ?", false).Find(&results).Error
	return results, err
}

func (r *Repository) GetUnsyncedOrders() ([]models.Order, error) {
	var results []models.Order
	err := r.db.Where("sincronizado = ?", false).Find(&results).Error
	return results, err
}

func (r *Repository) GetUnsyncedPayments() ([]models.Payment, error) {
	var results []models.Payment
	err := r.db.Where("sincronizado = ?", false).Find(&results).Error
	return results, err
}

func (r *Repository) GetUnsyncedProducts() ([]models.Product, error) {
	var results []models.Product
	err := r.db.Where("sincronizado = ?", false).Find(&results).Error
	return results, err
}

func (r *Repository) GetComandaToSync(id uint, dest *models.Comanda) error {
	return r.db.First(dest, id).Error
}

func (r *Repository) GetItemToSync(id uint, dest *models.ProductComanda) error {
	return r.db.First(dest, id).Error
}

func (r *Repository) GetPaymentToSync(id uint, dest *models.Payment) error {
	return r.db.First(dest, id).Error
}

func (r *Repository) GetProductToSync(id uint, dest *models.Product) error {
	return r.db.First(dest, id).Error
}

func (r *Repository) GetOrderToSync(id uint, dest *models.Order) error {
	return r.db.First(dest, id).Error
}

func (r *Repository) DeleteByID(modelName string, id uint) error {
	var model interface{}
	switch modelName {
	case "Product":
		model = &models.Product{}
	case "Comanda":
		model = &models.Comanda{}
	case "ProductComanda":
		model = &models.ProductComanda{}
	case "Order":
		model = &models.Order{}
	case "Client":
		model = &models.Client{}
	case "Categories":
		model = &models.Category{}
	case "Mesa":
		model = &models.Mesa{}
	case "Payments":
		model = &models.Payment{}
	default:
		return nil
	}
	return r.db.Delete(model, id).Error
}

func (r *Repository) UpdateComandaFields(id uint, updates map[string]interface{}) error {
	mappedUpdates := make(map[string]interface{})
	for k, v := range updates {
		switch k {
		case "mesa":
			mappedUpdates["mesa_id"] = v
		case "user":
			mappedUpdates["user_id"] = v
		case "client":
			mappedUpdates["client_id"] = v
		case "type_pay":
			mappedUpdates["type_pay_id"] = v
		default:
			mappedUpdates[k] = v
		}
	}

	// Mark as unsynced so the background worker pushes the update
	mappedUpdates["sincronizado"] = false

	return r.db.Model(&models.Comanda{}).Where("id = ?", id).Updates(mappedUpdates).Error
}
