// Copyright (c) 2021 - 2023, Ludvig Lundgren and the autobrr contributors.
// SPDX-License-Identifier: GPL-2.0-or-later

package database

import (
	"context"
	"database/sql"
	"time"

	"github.com/autobrr/autobrr/internal/domain"
	"github.com/autobrr/autobrr/internal/logger"
	"github.com/autobrr/autobrr/pkg/errors"

	sq "github.com/Masterminds/squirrel"
	"github.com/rs/zerolog"
)

type FeedCacheRepo struct {
	log zerolog.Logger
	db  *DB
}

func NewFeedCacheRepo(log logger.Logger, db *DB) domain.FeedCacheRepo {
	return &FeedCacheRepo{
		log: log.With().Str("module", "database").Str("repo", "feed_cache").Logger(),
		db:  db,
	}
}

func (r *FeedCacheRepo) Get(bucket string, key string) ([]byte, error) {
	queryBuilder := r.db.squirrel.
		Select(
			"value",
			"ttl",
		).
		From("feed_cache").
		Where(sq.Eq{"bucket": bucket}).
		Where(sq.Eq{"key": key}).
		Where(sq.Gt{"ttl": time.Now()})

	query, args, err := queryBuilder.ToSql()
	if err != nil {
		return nil, errors.Wrap(err, "error building query")
	}

	row := r.db.handler.QueryRow(query, args...)
	if err := row.Err(); err != nil {
		return nil, errors.Wrap(err, "error executing query")
	}

	var value []byte
	var ttl time.Duration

	if err := row.Scan(&value, &ttl); err != nil && err != sql.ErrNoRows {
		return nil, errors.Wrap(err, "error scanning row")
	}

	return value, nil
}

func (r *FeedCacheRepo) GetByBucket(ctx context.Context, bucket string) ([]domain.FeedCacheItem, error) {
	queryBuilder := r.db.squirrel.
		Select(
			"bucket",
			"key",
			"value",
			"ttl",
		).
		From("feed_cache").
		Where(sq.Eq{"bucket": bucket})

	query, args, err := queryBuilder.ToSql()
	if err != nil {
		return nil, errors.Wrap(err, "error building query")
	}

	rows, err := r.db.handler.QueryContext(ctx, query, args...)
	if err != nil {
		return nil, errors.Wrap(err, "error executing query")
	}

	defer rows.Close()

	var data []domain.FeedCacheItem

	for rows.Next() {
		var d domain.FeedCacheItem

		if err := rows.Scan(&d.Bucket, &d.Key, &d.Value, &d.TTL); err != nil {
			return nil, errors.Wrap(err, "error scanning row")
		}

		data = append(data, d)
	}

	if err := rows.Err(); err != nil {
		return nil, errors.Wrap(err, "row error")
	}

	return data, nil
}

func (r *FeedCacheRepo) GetCountByBucket(ctx context.Context, bucket string) (int, error) {

	queryBuilder := r.db.squirrel.
		Select("COUNT(*)").
		From("feed_cache").
		Where(sq.Eq{"bucket": bucket})

	query, args, err := queryBuilder.ToSql()
	if err != nil {
		return 0, errors.Wrap(err, "error building query")
	}

	row := r.db.handler.QueryRowContext(ctx, query, args...)
	if err != nil {
		return 0, errors.Wrap(err, "error executing query")
	}

	var count = 0

	if err := row.Scan(&count); err != nil {
		return 0, errors.Wrap(err, "error scanning row")
	}

	return count, nil
}

func (r *FeedCacheRepo) Exists(bucket string, key string) (bool, error) {
	queryBuilder := r.db.squirrel.
		Select("1").
		Prefix("SELECT EXISTS (").
		From("feed_cache").
		Where(sq.Eq{"bucket": bucket}).
		Where(sq.Eq{"key": key}).
		Suffix(")")

	query, args, err := queryBuilder.ToSql()
	if err != nil {
		return false, errors.Wrap(err, "error building query")
	}

	var exists bool
	err = r.db.handler.QueryRow(query, args...).Scan(&exists)
	if err != nil && err != sql.ErrNoRows {
		return false, errors.Wrap(err, "error query")
	}

	return exists, nil
}

func (r *FeedCacheRepo) Put(bucket string, key string, val []byte, ttl time.Time) error {
	queryBuilder := r.db.squirrel.
		Insert("feed_cache").
		Columns("bucket", "key", "value", "ttl").
		Values(bucket, key, val, ttl)

	query, args, err := queryBuilder.ToSql()
	if err != nil {
		return errors.Wrap(err, "error building query")
	}

	if _, err = r.db.handler.Exec(query, args...); err != nil {
		return errors.Wrap(err, "error executing query")
	}

	return nil
}

func (r *FeedCacheRepo) Delete(ctx context.Context, bucket string, key string) error {
	queryBuilder := r.db.squirrel.
		Delete("feed_cache").
		Where(sq.Eq{"bucket": bucket}).
		Where(sq.Eq{"key": key})

	query, args, err := queryBuilder.ToSql()
	if err != nil {
		return errors.Wrap(err, "error building query")
	}

	_, err = r.db.handler.ExecContext(ctx, query, args...)
	if err != nil {
		return errors.Wrap(err, "error executing query")
	}

	return nil
}

func (r *FeedCacheRepo) DeleteBucket(ctx context.Context, bucket string) error {
	queryBuilder := r.db.squirrel.
		Delete("feed_cache").
		Where(sq.Eq{"bucket": bucket})

	query, args, err := queryBuilder.ToSql()
	if err != nil {
		return errors.Wrap(err, "error building query")
	}

	result, err := r.db.handler.ExecContext(ctx, query, args...)
	if err != nil {
		return errors.Wrap(err, "error executing query")
	}

	rows, err := result.RowsAffected()
	if err != nil {
		return errors.Wrap(err, "error exec result")
	}

	if rows == 0 {
		return errors.Wrap(err, "error no rows affected")
	}

	return nil
}
