package backend

import (
	"context"
	"database/sql"
	"errors"
	"fmt"

	"git.faercol.me/faercol/polyculeconnect/polyculeconnect/internal/model"
	"git.faercol.me/faercol/polyculeconnect/polyculeconnect/logger"
	"github.com/google/uuid"
)

var ErrNotFound = errors.New("backend not found")

const backendRows = `"id", "name", "oidc_issuer", "oidc_client_id", "oidc_client_secret", "oidc_redirect_uri"`

type scannable interface {
	Scan(dest ...any) error
}

type BackendDB interface {
	GetAllBackends(ctx context.Context) ([]*model.Backend, error)

	GetBackendByID(ctx context.Context, id uuid.UUID) (*model.Backend, error)
	GetBackendByName(ctx context.Context, name string) (*model.Backend, error)

	AddBackend(ctx context.Context, newBackend *model.Backend) error

	DeleteBackend(ctx context.Context, id uuid.UUID) error
}

type sqlBackendDB struct {
	db *sql.DB
}

func backendFromRow(row scannable) (*model.Backend, error) {
	var res model.Backend

	if err := row.Scan(&res.ID, &res.Name, &res.Config.Issuer, &res.Config.ClientID, &res.Config.ClientSecret, &res.Config.RedirectURI); err != nil {
		if errors.Is(err, sql.ErrNoRows) {
			return nil, ErrNotFound
		}
		return nil, fmt.Errorf("invalid format for backend: %w", err)
	}
	return &res, nil
}

func (db *sqlBackendDB) GetBackendByName(ctx context.Context, name string) (*model.Backend, error) {
	logger.L.Debugf("Getting backend with name %s from DB", name)
	query := fmt.Sprintf(`SELECT %s FROM "backend" WHERE "name" = ?`, backendRows)
	row := db.db.QueryRowContext(ctx, query, name)
	return backendFromRow(row)
}

func (db *sqlBackendDB) GetBackendByID(ctx context.Context, id uuid.UUID) (*model.Backend, error) {
	logger.L.Debugf("Getting backend with ID %s from DB", id)
	query := fmt.Sprintf(`SELECT %s FROM "backend" WHERE "id" = ?`, backendRows)
	row := db.db.QueryRowContext(ctx, query, id)
	return backendFromRow(row)
}

func (db *sqlBackendDB) GetAllBackends(ctx context.Context) ([]*model.Backend, error) {
	logger.L.Debug("Getting all backends from DB")
	rows, err := db.db.QueryContext(ctx, fmt.Sprintf(`SELECT %s FROM "backend"`, backendRows))
	if err != nil {
		return nil, err
	}

	var res []*model.Backend
	for rows.Next() {
		b, err := backendFromRow(rows)
		if err != nil {
			return nil, err
		}
		res = append(res, b)
	}
	return res, rows.Err()
}

func (db *sqlBackendDB) AddBackend(ctx context.Context, newBackend *model.Backend) error {
	tx, err := db.db.BeginTx(ctx, nil)
	if err != nil {
		return fmt.Errorf("failed to start transaction: %w", err)
	}
	defer func() { _ = tx.Rollback() }()

	query := fmt.Sprintf(`INSERT INTO "backend" (%s) VALUES ($1, $2, $3, $4, $5, $6)`, backendRows)
	_, err = tx.ExecContext(
		ctx, query,
		newBackend.ID, newBackend.Name,
		newBackend.Config.Issuer, newBackend.Config.ClientID,
		newBackend.Config.ClientSecret, newBackend.Config.RedirectURI,
	)
	if err != nil {
		return fmt.Errorf("failed to insert in DB: %w", err)
	}

	if err := tx.Commit(); err != nil {
		return fmt.Errorf("failed to commit transaction: %w", err)
	}
	return nil
}

func (db *sqlBackendDB) DeleteBackend(ctx context.Context, id uuid.UUID) error {
	tx, err := db.db.BeginTx(ctx, nil)
	if err != nil {
		return fmt.Errorf("failed to start transaction: %w", err)
	}
	defer func() { _ = tx.Rollback() }()

	if _, err := tx.ExecContext(ctx, `DELETE FROM "backend" WHERE id = $1`, id.String()); err != nil {
		return fmt.Errorf("failed to run query: %w", err)
	}

	if err := tx.Commit(); err != nil {
		return fmt.Errorf("failed to commit transaction: %w", err)
	}
	return nil
}

func New(db *sql.DB) *sqlBackendDB {
	return &sqlBackendDB{db: db}
}
