package models

import (
	"context"
	"time"

	"quyun/app/requests"
	"quyun/database/fields"
	"quyun/database/schemas/public/model"
	"quyun/database/schemas/public/table"

	. "github.com/go-jet/jet/v2/postgres"
	"github.com/samber/lo"
	"github.com/sirupsen/logrus"
)

// @provider
type mediasModel struct {
	log *logrus.Entry `inject:"false"`
}

func (m *mediasModel) Prepare() error {
	m.log = logrus.WithField("module", "mediasModel")
	return nil
}

func (m *mediasModel) BuildConditionWithKey(key *string) BoolExpression {
	tbl := table.Medias

	cond := Bool(true)

	if key == nil || *key == "" {
		return cond
	}

	cond = cond.AND(
		tbl.Name.LIKE(String("%" + *key + "%")),
	)

	return cond
}

// countByCond
func (m *mediasModel) countByCondition(ctx context.Context, expr BoolExpression) (int64, error) {
	var cnt struct {
		Cnt int64
	}

	tbl := table.Medias
	stmt := SELECT(COUNT(tbl.ID).AS("cnt")).FROM(tbl).WHERE(expr)
	m.log.Infof("sql: %s", stmt.DebugSql())

	err := stmt.QueryContext(ctx, db, &cnt)
	if err != nil {
		m.log.Errorf("error counting media items: %v", err)
		return 0, err
	}

	return cnt.Cnt, nil
}

func (m *mediasModel) List(ctx context.Context, pagination *requests.Pagination, expr BoolExpression) (*requests.Pager, error) {
	pagination.Format()

	tbl := table.Medias
	stmt := tbl.
		SELECT(tbl.AllColumns).
		WHERE(expr).
		ORDER_BY(tbl.ID.DESC()).
		LIMIT(pagination.Limit).
		OFFSET(pagination.Offset)
	m.log.Infof("sql: %s", stmt.DebugSql())

	var medias []model.Medias
	err := stmt.QueryContext(ctx, db, &medias)
	if err != nil {
		m.log.Errorf("error querying media items: %v", err)
		return nil, err
	}

	count, err := m.countByCondition(ctx, expr)
	if err != nil {
		m.log.Errorf("error getting media count: %v", err)
		return nil, err
	}

	return &requests.Pager{
		Items:      medias,
		Total:      count,
		Pagination: *pagination,
	}, nil
}

func (m *mediasModel) BatchCreate(ctx context.Context, models []*model.Medias) error {
	stmt := table.Medias.INSERT(table.Medias.MutableColumns).MODELS(models)
	m.log.Infof("sql: %s", stmt.DebugSql())

	if _, err := stmt.ExecContext(ctx, db); err != nil {
		m.log.Errorf("error creating media item: %v", err)
		return err
	}

	m.log.Infof("media item created successfully")
	return nil
}

func (m *mediasModel) Create(ctx context.Context, model *model.Medias) error {
	model.CreatedAt = time.Now()
	stmt := table.Medias.INSERT(table.Medias.MutableColumns).MODEL(model).RETURNING(table.Medias.AllColumns)
	m.log.Infof("sql: %s", stmt.DebugSql())

	if err := stmt.QueryContext(ctx, db, model); err != nil {
		m.log.Errorf("error creating media item: %v", err)
		return err
	}

	m.log.Infof("media item created successfully")
	return nil
}

// GetByIds
func (m *mediasModel) GetByIds(ctx context.Context, ids []int64) ([]*model.Medias, error) {
	if len(ids) == 0 {
		return nil, nil
	}

	condIds := lo.Map(ids, func(id int64, _ int) Expression {
		return Int64(id)
	})

	tbl := table.Medias
	stmt := tbl.
		SELECT(tbl.AllColumns).
		WHERE(tbl.ID.IN(condIds...))
	m.log.Infof("sql: %s", stmt.DebugSql())

	var medias []model.Medias
	err := stmt.QueryContext(ctx, db, &medias)
	if err != nil {
		m.log.Errorf("error querying media items: %v", err)
		return nil, err
	}

	return lo.Map(medias, func(media model.Medias, _ int) *model.Medias {
		return &media
	}), nil
}

// GetByHash
func (m *mediasModel) GetByHash(ctx context.Context, hash string) (*model.Medias, error) {
	tbl := table.Medias
	stmt := tbl.
		SELECT(tbl.AllColumns).
		WHERE(tbl.Hash.EQ(String(hash)))
	m.log.Infof("sql: %s", stmt.DebugSql())

	var media model.Medias
	err := stmt.QueryContext(ctx, db, &media)
	if err != nil {
		m.log.Errorf("error querying media item by hash: %v", err)
		return nil, err
	}

	return &media, nil
}

// Update
func (m *mediasModel) Update(ctx context.Context, hash string, model *model.Medias) error {
	tbl := table.Medias
	stmt := tbl.
		UPDATE(tbl.MutableColumns.Except(tbl.CreatedAt)).
		MODEL(model).
		WHERE(table.Medias.Hash.EQ(String(hash)))
	m.log.Infof("sql: %s", stmt.DebugSql())

	if _, err := stmt.ExecContext(ctx, db); err != nil {
		m.log.WithField("hash", hash).Errorf("error updating media item: %v", err)
		return err
	}

	m.log.Infof("media item updated successfully")
	return nil
}

// GetByID
func (m *mediasModel) GetByID(ctx context.Context, id int64) (*model.Medias, error) {
	tbl := table.Medias
	stmt := tbl.
		SELECT(tbl.AllColumns).
		WHERE(tbl.ID.EQ(Int64(id)))
	m.log.Infof("sql: %s", stmt.DebugSql())

	var media model.Medias

	if err := stmt.QueryContext(ctx, db, &media); err != nil {
		m.log.Errorf("error querying media item by ID: %v", err)
		return nil, err
	}

	return &media, nil
}

// Delete
func (m *mediasModel) Delete(ctx context.Context, id int64) error {
	tbl := table.Medias
	stmt := tbl.
		DELETE().
		WHERE(tbl.ID.EQ(Int64(id)))
	m.log.Infof("sql: %s", stmt.DebugSql())

	if _, err := stmt.ExecContext(ctx, db); err != nil {
		m.log.Errorf("error deleting media item: %v", err)
		return err
	}

	m.log.Infof("media item deleted successfully")
	return nil
}

// UpdateMetas
func (m *mediasModel) UpdateMetas(ctx context.Context, id int64, metas fields.MediaMetas) error {
	meta := fields.ToJson(metas)

	tbl := table.Medias
	stmt := tbl.
		UPDATE(tbl.Metas).
		SET(meta).
		WHERE(tbl.ID.EQ(Int64(id)))
	m.log.Infof("sql: %s", stmt.DebugSql())

	if _, err := stmt.ExecContext(ctx, db); err != nil {
		m.log.Errorf("error updating media metas: %v", err)
		return err
	}

	m.log.Infof("media (%d) metas updated successfully", id)
	return nil
}

// GetRelationMedias
func (m *mediasModel) GetRelations(ctx context.Context, hash string) ([]*model.Medias, error) {
	tbl := table.Medias
	stmt := tbl.
		SELECT(tbl.AllColumns).
		WHERE(
			RawBool("metas->>'parent_hash' = ?", RawArgs{"?": hash}),
		)
	m.log.Infof("sql: %s", stmt.DebugSql())

	var medias []model.Medias
	if err := stmt.QueryContext(ctx, db, &medias); err != nil {
		m.log.Errorf("error querying media items: %v", err)
		return nil, err
	}

	return lo.Map(medias, func(media model.Medias, _ int) *model.Medias {
		return &media
	}), nil
}

// Count
func (m *mediasModel) Count(ctx context.Context) (int64, error) {
	tbl := table.Medias
	stmt := tbl.SELECT(COUNT(tbl.ID).AS("count"))
	m.log.Infof("sql: %s", stmt.DebugSql())

	var count struct {
		Count int64
	}

	if err := stmt.QueryContext(ctx, db, &count); err != nil {
		m.log.Errorf("error counting media items: %v", err)
		return 0, err
	}

	return count.Count, nil
}
