package db

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

type querier interface {
	ExecContext(context.Context, string, ...any) (sql.Result, error)
	QueryContext(context.Context, string, ...any) (*sql.Rows, error)
	QueryRowContext(context.Context, string, ...any) *sql.Row
}

type Store struct {
	DB        *sql.DB
	Users     *UserRepository
	Projects  *ProjectRepository
	Tags      *TagRepository
	Releases  *ReleaseRepository
	APIKeys   *APIKeyRepository
	Sessions  *SessionRepository
	AuditLogs *AuditLogRepository
}

type TxStore struct {
	Tx        *sql.Tx
	Users     *UserRepository
	Projects  *ProjectRepository
	Tags      *TagRepository
	Releases  *ReleaseRepository
	APIKeys   *APIKeyRepository
	Sessions  *SessionRepository
	AuditLogs *AuditLogRepository
}

type UserRepository struct {
	q querier
}

type ProjectRepository struct {
	q querier
}

type TagRepository struct {
	q querier
}

type ReleaseRepository struct {
	q querier
}

type APIKeyRepository struct {
	q querier
}

type SessionRepository struct {
	q querier
}

type AuditLogRepository struct {
	q querier
}

func NewStore(database *sql.DB) *Store {
	return &Store{
		DB:        database,
		Users:     &UserRepository{q: database},
		Projects:  &ProjectRepository{q: database},
		Tags:      &TagRepository{q: database},
		Releases:  &ReleaseRepository{q: database},
		APIKeys:   &APIKeyRepository{q: database},
		Sessions:  &SessionRepository{q: database},
		AuditLogs: &AuditLogRepository{q: database},
	}
}

func (s *Store) Close() error {
	if s == nil || s.DB == nil {
		return nil
	}

	return s.DB.Close()
}

func (s *Store) HealthCheck(ctx context.Context) error {
	if s == nil || s.DB == nil {
		return fmt.Errorf("database store is not initialized")
	}

	return s.DB.PingContext(ctx)
}

func (s *Store) WithTx(ctx context.Context, fn func(*TxStore) error) error {
	if s == nil || s.DB == nil {
		return fmt.Errorf("database store is not initialized")
	}

	tx, err := s.DB.BeginTx(ctx, nil)
	if err != nil {
		return fmt.Errorf("begin transaction: %w", err)
	}

	if err := fn(newTxStore(tx)); err != nil {
		if rollbackErr := tx.Rollback(); rollbackErr != nil && !errors.Is(rollbackErr, sql.ErrTxDone) {
			return errors.Join(err, fmt.Errorf("rollback transaction: %w", rollbackErr))
		}

		return err
	}

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

	return nil
}

func newTxStore(tx *sql.Tx) *TxStore {
	return &TxStore{
		Tx:        tx,
		Users:     &UserRepository{q: tx},
		Projects:  &ProjectRepository{q: tx},
		Tags:      &TagRepository{q: tx},
		Releases:  &ReleaseRepository{q: tx},
		APIKeys:   &APIKeyRepository{q: tx},
		Sessions:  &SessionRepository{q: tx},
		AuditLogs: &AuditLogRepository{q: tx},
	}
}
