// Copyright (c) 2021 - 2024, 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 ProxyRepo struct {
	log zerolog.Logger
	db  *DB
}

func NewProxyRepo(log logger.Logger, db *DB) domain.ProxyRepo {
	return &ProxyRepo{
		log: log.With().Str("repo", "proxy").Logger(),
		db:  db,
	}
}

func (r *ProxyRepo) Store(ctx context.Context, p *domain.Proxy) error {
	queryBuilder := r.db.squirrel.
		Insert("proxy").
		Columns(
			"enabled",
			"name",
			"type",
			"addr",
			"auth_user",
			"auth_pass",
			"timeout",
		).
		Values(
			p.Enabled,
			p.Name,
			p.Type,
			toNullString(p.Addr),
			toNullString(p.User),
			toNullString(p.Pass),
			p.Timeout,
		).
		Suffix("RETURNING id").
		RunWith(r.db.handler)

	var retID int64
	err := queryBuilder.QueryRowContext(ctx).Scan(&retID)
	if err != nil {
		return errors.Wrap(err, "error executing query")
	}

	p.ID = retID

	return nil
}

func (r *ProxyRepo) Update(ctx context.Context, p *domain.Proxy) error {
	queryBuilder := r.db.squirrel.
		Update("proxy").
		Set("enabled", p.Enabled).
		Set("name", p.Name).
		Set("type", p.Type).
		Set("addr", p.Addr).
		Set("auth_user", toNullString(p.User)).
		Set("auth_pass", toNullString(p.Pass)).
		Set("timeout", p.Timeout).
		Set("updated_at", time.Now().Format(time.RFC3339)).
		Where(sq.Eq{"id": p.ID})

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

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

	rowsAffected, err := res.RowsAffected()
	if err != nil {
		return errors.Wrap(err, "error getting affected rows")
	}

	if rowsAffected == 0 {
		return domain.ErrUpdateFailed
	}

	return err
}

func (r *ProxyRepo) List(ctx context.Context) ([]domain.Proxy, error) {
	queryBuilder := r.db.squirrel.
		Select(
			"id",
			"enabled",
			"name",
			"type",
			"addr",
			"auth_user",
			"auth_pass",
			"timeout",
		).
		From("proxy").
		OrderBy("name ASC")

	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()

	proxies := make([]domain.Proxy, 0)
	for rows.Next() {
		var proxy domain.Proxy

		var user, pass sql.NullString

		if err := rows.Scan(&proxy.ID, &proxy.Enabled, &proxy.Name, &proxy.Type, &proxy.Addr, &user, &pass, &proxy.Timeout); err != nil {
			return nil, errors.Wrap(err, "error scanning row")
		}

		proxy.User = user.String
		proxy.Pass = pass.String

		proxies = append(proxies, proxy)
	}

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

	return proxies, nil
}

func (r *ProxyRepo) Delete(ctx context.Context, id int64) error {
	tx, err := r.db.BeginTx(ctx, nil)
	if err != nil {
		return errors.Wrap(err, "error begin transaction")
	}

	defer tx.Rollback()

	queryBuilder := r.db.squirrel.
		Delete("proxy").
		Where(sq.Eq{"id": id})

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

	res, err := tx.ExecContext(ctx, query, args...)
	if err != nil {
		return errors.Wrap(err, "error executing query")
	}

	rowsAffected, err := res.RowsAffected()
	if err != nil {
		return errors.Wrap(err, "error getting affected rows")
	}

	if rowsAffected == 0 {
		return domain.ErrDeleteFailed
	}

	err = tx.Commit()
	if err != nil {
		return errors.Wrap(err, "error commit deleting proxy")
	}

	return nil
}

func (r *ProxyRepo) FindByID(ctx context.Context, id int64) (*domain.Proxy, error) {
	queryBuilder := r.db.squirrel.
		Select(
			"id",
			"enabled",
			"name",
			"type",
			"addr",
			"auth_user",
			"auth_pass",
			"timeout",
		).
		From("proxy").
		OrderBy("name ASC").
		Where(sq.Eq{"id": id})

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

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

	var proxy domain.Proxy

	var user, pass sql.NullString

	err = row.Scan(&proxy.ID, &proxy.Enabled, &proxy.Name, &proxy.Type, &proxy.Addr, &user, &pass, &proxy.Timeout)
	if err != nil {
		if errors.Is(err, sql.ErrNoRows) {
			return nil, domain.ErrRecordNotFound
		}

		return nil, errors.Wrap(err, "error scanning row")
	}

	proxy.User = user.String
	proxy.Pass = pass.String

	return &proxy, nil
}

func (r *ProxyRepo) ToggleEnabled(ctx context.Context, id int64, enabled bool) error {
	queryBuilder := r.db.squirrel.
		Update("proxy").
		Set("enabled", enabled).
		Set("updated_at", time.Now().Format(time.RFC3339)).
		Where(sq.Eq{"id": id})

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

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

	rowsAffected, err := res.RowsAffected()
	if err != nil {
		return errors.Wrap(err, "error getting affected rows")
	}

	if rowsAffected == 0 {
		return domain.ErrUpdateFailed
	}

	return nil
}
