package pg

import (
	"context"
	"errors"

	"github.com/google/uuid"
	"github.com/samber/lo"
	"gorm.io/gorm"

	"github.com/chaitin/panda-wiki/domain"
	"github.com/chaitin/panda-wiki/log"
	"github.com/chaitin/panda-wiki/store/pg"
)

type AppRepository struct {
	db     *pg.DB
	logger *log.Logger
}

func NewAppRepository(db *pg.DB, logger *log.Logger) *AppRepository {
	return &AppRepository{
		db:     db,
		logger: logger.WithModule("repo.pg.app"),
	}
}

func (r *AppRepository) GetAppDetail(ctx context.Context, id string) (*domain.App, error) {
	app := &domain.App{}
	if err := r.db.WithContext(ctx).
		Model(&domain.App{}).
		Where("id = ?", id).
		First(app).Error; err != nil {
		return nil, err
	}
	return app, nil
}

func (r *AppRepository) UpdateApp(ctx context.Context, id, kbId string, appRequest *domain.UpdateAppReq) error {
	updateMap := map[string]any{}
	if appRequest.Name != nil {
		updateMap["name"] = appRequest.Name
	}
	if appRequest.Settings != nil {
		updateMap["settings"] = appRequest.Settings
	}
	return r.db.WithContext(ctx).Model(&domain.App{}).Where("id = ? and kb_id = ?", id, kbId).Updates(updateMap).Error
}

func (r *AppRepository) DeleteApp(ctx context.Context, id, kbId string) error {
	return r.db.WithContext(ctx).Delete(&domain.App{}, "id = ? and kb_id = ?", id, kbId).Error
}

func (r *AppRepository) GetOrCreateAppByKBIDAndType(ctx context.Context, kbID string, appType domain.AppType) (*domain.App, error) {
	app := &domain.App{}
	if err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		err := tx.Model(&domain.App{}).Where("kb_id = ? AND type = ?", kbID, appType).First(app).Error
		if err != nil {
			if errors.Is(err, gorm.ErrRecordNotFound) {
				// create app if kb is exist
				if err := tx.Model(&domain.KnowledgeBase{}).Where("id = ?", kbID).First(&domain.KnowledgeBase{}).Error; err != nil {
					return err
				}
				app = &domain.App{
					ID:   uuid.New().String(),
					KBID: kbID,
					Type: appType,
				}
				return tx.Create(app).Error
			}
			return err
		}
		return nil
	}); err != nil {
		return nil, err
	}
	return app, nil
}

// GetAppsByTypes returns all apps of a specific type
func (r *AppRepository) GetAppsByTypes(ctx context.Context, appTypes []domain.AppType) ([]*domain.App, error) {
	var apps []*domain.App
	if err := r.db.WithContext(ctx).
		Model(&domain.App{}).
		Where("type IN (?)", appTypes).
		Find(&apps).Error; err != nil {
		return nil, err
	}
	return apps, nil
}

func (r *AppRepository) GetAppList(ctx context.Context, kbID string) (map[string]*domain.App, error) {
	var apps []*domain.App
	if err := r.db.WithContext(ctx).
		Model(&domain.App{}).
		Where("kb_id = ?", kbID).
		Find(&apps).Error; err != nil {
		return nil, err
	}
	return lo.SliceToMap(apps, func(app *domain.App) (string, *domain.App) {
		return app.ID, app
	}), nil
}
