package trash

import (
	"context"
	"errors"
	"fmt"
	"rijig/model"
	"time"

	"gorm.io/gorm"
)

type TrashRepositoryInterface interface {
	CreateTrashCategory(ctx context.Context, category *model.TrashCategory) error
	CreateTrashCategoryWithDetails(ctx context.Context, category *model.TrashCategory, details []model.TrashDetail) error
	UpdateTrashCategory(ctx context.Context, id string, updates map[string]interface{}) error
	GetAllTrashCategories(ctx context.Context) ([]model.TrashCategory, error)
	GetAllTrashCategoriesWithDetails(ctx context.Context) ([]model.TrashCategory, error)
	GetTrashCategoryByID(ctx context.Context, id string) (*model.TrashCategory, error)
	GetTrashCategoryByIDWithDetails(ctx context.Context, id string) (*model.TrashCategory, error)
	DeleteTrashCategory(ctx context.Context, id string) error

	CreateTrashDetail(ctx context.Context, detail *model.TrashDetail) error
	AddTrashDetailToCategory(ctx context.Context, categoryID string, detail *model.TrashDetail) error
	UpdateTrashDetail(ctx context.Context, id string, updates map[string]interface{}) error
	GetTrashDetailsByCategory(ctx context.Context, categoryID string) ([]model.TrashDetail, error)
	GetTrashDetailByID(ctx context.Context, id string) (*model.TrashDetail, error)
	DeleteTrashDetail(ctx context.Context, id string) error

	CheckTrashCategoryExists(ctx context.Context, id string) (bool, error)
	CheckTrashDetailExists(ctx context.Context, id string) (bool, error)
	GetMaxStepOrderByCategory(ctx context.Context, categoryID string) (int, error)
}

type TrashRepository struct {
	db *gorm.DB
}

func NewTrashRepository(db *gorm.DB) TrashRepositoryInterface {
	return &TrashRepository{
		db: db,
	}
}

func (r *TrashRepository) CreateTrashCategory(ctx context.Context, category *model.TrashCategory) error {
	if err := r.db.WithContext(ctx).Create(category).Error; err != nil {
		return fmt.Errorf("failed to create trash category: %w", err)
	}
	return nil
}

func (r *TrashRepository) CreateTrashCategoryWithDetails(ctx context.Context, category *model.TrashCategory, details []model.TrashDetail) error {
	return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {

		if err := tx.Create(category).Error; err != nil {
			return fmt.Errorf("failed to create trash category: %w", err)
		}

		if len(details) > 0 {

			for i := range details {
				details[i].TrashCategoryID = category.ID

				if details[i].StepOrder == 0 {
					details[i].StepOrder = i + 1
				}
			}

			if err := tx.Create(&details).Error; err != nil {
				return fmt.Errorf("failed to create trash details: %w", err)
			}
		}

		return nil
	})
}

func (r *TrashRepository) UpdateTrashCategory(ctx context.Context, id string, updates map[string]interface{}) error {

	exists, err := r.CheckTrashCategoryExists(ctx, id)
	if err != nil {
		return err
	}
	if !exists {
		return errors.New("trash category not found")
	}

	updates["updated_at"] = time.Now()

	result := r.db.WithContext(ctx).Model(&model.TrashCategory{}).Where("id = ?", id).Updates(updates)
	if result.Error != nil {
		return fmt.Errorf("failed to update trash category: %w", result.Error)
	}

	if result.RowsAffected == 0 {
		return errors.New("no rows affected during update")
	}

	return nil
}

func (r *TrashRepository) GetAllTrashCategories(ctx context.Context) ([]model.TrashCategory, error) {
	var categories []model.TrashCategory

	if err := r.db.WithContext(ctx).Find(&categories).Error; err != nil {
		return nil, fmt.Errorf("failed to get trash categories: %w", err)
	}

	return categories, nil
}

func (r *TrashRepository) GetAllTrashCategoriesWithDetails(ctx context.Context) ([]model.TrashCategory, error) {
	var categories []model.TrashCategory

	if err := r.db.WithContext(ctx).Preload("Details", func(db *gorm.DB) *gorm.DB {
		return db.Order("step_order ASC")
	}).Find(&categories).Error; err != nil {
		return nil, fmt.Errorf("failed to get trash categories with details: %w", err)
	}

	return categories, nil
}

func (r *TrashRepository) GetTrashCategoryByID(ctx context.Context, id string) (*model.TrashCategory, error) {
	var category model.TrashCategory

	if err := r.db.WithContext(ctx).Where("id = ?", id).First(&category).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, errors.New("trash category not found")
		}
		return nil, fmt.Errorf("failed to get trash category: %w", err)
	}

	return &category, nil
}

func (r *TrashRepository) GetTrashCategoryByIDWithDetails(ctx context.Context, id string) (*model.TrashCategory, error) {
	var category model.TrashCategory

	if err := r.db.WithContext(ctx).Preload("Details", func(db *gorm.DB) *gorm.DB {
		return db.Order("step_order ASC")
	}).Where("id = ?", id).First(&category).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, errors.New("trash category not found")
		}
		return nil, fmt.Errorf("failed to get trash category with details: %w", err)
	}

	return &category, nil
}

func (r *TrashRepository) DeleteTrashCategory(ctx context.Context, id string) error {

	exists, err := r.CheckTrashCategoryExists(ctx, id)
	if err != nil {
		return err
	}
	if !exists {
		return errors.New("trash category not found")
	}

	result := r.db.WithContext(ctx).Delete(&model.TrashCategory{ID: id})
	if result.Error != nil {
		return fmt.Errorf("failed to delete trash category: %w", result.Error)
	}

	if result.RowsAffected == 0 {
		return errors.New("no rows affected during deletion")
	}

	return nil
}

func (r *TrashRepository) CreateTrashDetail(ctx context.Context, detail *model.TrashDetail) error {

	exists, err := r.CheckTrashCategoryExists(ctx, detail.TrashCategoryID)
	if err != nil {
		return err
	}
	if !exists {
		return errors.New("trash category not found")
	}

	if detail.StepOrder == 0 {
		maxOrder, err := r.GetMaxStepOrderByCategory(ctx, detail.TrashCategoryID)
		if err != nil {
			return err
		}
		detail.StepOrder = maxOrder + 1
	}

	if err := r.db.WithContext(ctx).Create(detail).Error; err != nil {
		return fmt.Errorf("failed to create trash detail: %w", err)
	}

	return nil
}

func (r *TrashRepository) AddTrashDetailToCategory(ctx context.Context, categoryID string, detail *model.TrashDetail) error {

	exists, err := r.CheckTrashCategoryExists(ctx, categoryID)
	if err != nil {
		return err
	}
	if !exists {
		return errors.New("trash category not found")
	}

	detail.TrashCategoryID = categoryID

	if detail.StepOrder == 0 {
		maxOrder, err := r.GetMaxStepOrderByCategory(ctx, categoryID)
		if err != nil {
			return err
		}
		detail.StepOrder = maxOrder + 1
	}

	if err := r.db.WithContext(ctx).Create(detail).Error; err != nil {
		return fmt.Errorf("failed to add trash detail to category: %w", err)
	}

	return nil
}

func (r *TrashRepository) UpdateTrashDetail(ctx context.Context, id string, updates map[string]interface{}) error {

	exists, err := r.CheckTrashDetailExists(ctx, id)
	if err != nil {
		return err
	}
	if !exists {
		return errors.New("trash detail not found")
	}

	updates["updated_at"] = time.Now()

	result := r.db.WithContext(ctx).Model(&model.TrashDetail{}).Where("id = ?", id).Updates(updates)
	if result.Error != nil {
		return fmt.Errorf("failed to update trash detail: %w", result.Error)
	}

	if result.RowsAffected == 0 {
		return errors.New("no rows affected during update")
	}

	return nil
}

func (r *TrashRepository) GetTrashDetailsByCategory(ctx context.Context, categoryID string) ([]model.TrashDetail, error) {
	var details []model.TrashDetail

	if err := r.db.WithContext(ctx).Where("trash_category_id = ?", categoryID).Order("step_order ASC").Find(&details).Error; err != nil {
		return nil, fmt.Errorf("failed to get trash details: %w", err)
	}

	return details, nil
}

func (r *TrashRepository) GetTrashDetailByID(ctx context.Context, id string) (*model.TrashDetail, error) {
	var detail model.TrashDetail

	if err := r.db.WithContext(ctx).Where("id = ?", id).First(&detail).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, errors.New("trash detail not found")
		}
		return nil, fmt.Errorf("failed to get trash detail: %w", err)
	}

	return &detail, nil
}

func (r *TrashRepository) DeleteTrashDetail(ctx context.Context, id string) error {

	exists, err := r.CheckTrashDetailExists(ctx, id)
	if err != nil {
		return err
	}
	if !exists {
		return errors.New("trash detail not found")
	}

	result := r.db.WithContext(ctx).Delete(&model.TrashDetail{ID: id})
	if result.Error != nil {
		return fmt.Errorf("failed to delete trash detail: %w", result.Error)
	}

	if result.RowsAffected == 0 {
		return errors.New("no rows affected during deletion")
	}

	return nil
}

func (r *TrashRepository) CheckTrashCategoryExists(ctx context.Context, id string) (bool, error) {
	var count int64

	if err := r.db.WithContext(ctx).Model(&model.TrashCategory{}).Where("id = ?", id).Count(&count).Error; err != nil {
		return false, fmt.Errorf("failed to check trash category existence: %w", err)
	}

	return count > 0, nil
}

func (r *TrashRepository) CheckTrashDetailExists(ctx context.Context, id string) (bool, error) {
	var count int64

	if err := r.db.WithContext(ctx).Model(&model.TrashDetail{}).Where("id = ?", id).Count(&count).Error; err != nil {
		return false, fmt.Errorf("failed to check trash detail existence: %w", err)
	}

	return count > 0, nil
}

func (r *TrashRepository) GetMaxStepOrderByCategory(ctx context.Context, categoryID string) (int, error) {
	var maxOrder int

	if err := r.db.WithContext(ctx).Model(&model.TrashDetail{}).
		Where("trash_category_id = ?", categoryID).
		Select("COALESCE(MAX(step_order), 0)").
		Scan(&maxOrder).Error; err != nil {
		return 0, fmt.Errorf("failed to get max step order: %w", err)
	}

	return maxOrder, nil
}
