// Code generated by candi v1.0.12.

package repository

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

	// @candi:repositoryImport
	memberrepo "monorepo/services/user-service/internal/modules/member/repository"

	"gorm.io/driver/postgres"
	"gorm.io/gorm"
	"pkg.agungdp.dev/candi/tracer"
)

type (
	// RepoSQL abstraction
	RepoSQL interface {
		WithTransaction(ctx context.Context, txFunc func(ctx context.Context, repo RepoSQL) error) (err error)
		Free()

		// @candi:repositoryMethod
		MemberRepo() memberrepo.MemberRepository
	}

	repoSQLImpl struct {
		readDB, writeDB *gorm.DB

		// register all repository from modules
		// @candi:repositoryField
		memberRepo memberrepo.MemberRepository
	}
)

var (
	globalRepoSQL RepoSQL
)

// setSharedRepoSQL set the global singleton "RepoSQL" implementation
func setSharedRepoSQL(readDB, writeDB *sql.DB) {
	gormRead, err := gorm.Open(postgres.New(postgres.Config{
		Conn: readDB,
	}), &gorm.Config{})

	if err != nil {
		panic(err)
	}

	gormWrite, err := gorm.Open(postgres.New(postgres.Config{
		Conn: writeDB,
	}), &gorm.Config{SkipDefaultTransaction: true})

	if err != nil {
		panic(err)
	}
	globalRepoSQL = NewRepositorySQL(gormRead, gormWrite)
}

// GetSharedRepoSQL returns the global singleton "RepoSQL" implementation
func GetSharedRepoSQL() RepoSQL {
	return globalRepoSQL
}

// NewRepositorySQL constructor
func NewRepositorySQL(readDB, writeDB *gorm.DB) RepoSQL {

	return &repoSQLImpl{
		readDB: readDB, writeDB: writeDB,

		// @candi:repositoryConstructor
		memberRepo: memberrepo.NewMemberRepoSQL(readDB, writeDB),
	}
}

// WithTransaction run transaction for each repository with context, include handle canceled or timeout context
func (r *repoSQLImpl) WithTransaction(ctx context.Context, txFunc func(ctx context.Context, repo RepoSQL) error) (err error) {
	trace := tracer.StartTrace(ctx, "RepoSQL:Transaction")
	defer trace.Finish()
	ctx = trace.Context()

	tx := r.writeDB.Begin()
	err = tx.Error
	if err != nil {
		return err
	}

	// reinit new repository in different memory address with tx value
	manager := NewRepositorySQL(r.readDB, tx)
	defer func() {
		if err != nil {
			tx.Rollback()
			trace.SetError(err)
		} else {
			tx.Commit()
		}
		manager.Free()
	}()

	errChan := make(chan error)
	go func() {
		defer func() {
			if r := recover(); r != nil {
				errChan <- fmt.Errorf("panic: %v", r)
			}
			close(errChan)
		}()

		if err := txFunc(ctx, manager); err != nil {
			errChan <- err
		}
	}()

	select {
	case <-ctx.Done():
		return fmt.Errorf("Canceled or timeout: %v", ctx.Err())
	case e := <-errChan:
		return e
	}
}

func (r *repoSQLImpl) Free() {
	// make nil all repository
	// @candi:repositoryDestructor
	r.memberRepo = nil
}

// @candi:repositoryImplementation
func (r *repoSQLImpl) MemberRepo() memberrepo.MemberRepository {
	return r.memberRepo
}
