package logic

import (
	"blackforestbytes.com/simplecloudnotifier/api/apierr"
	"blackforestbytes.com/simplecloudnotifier/api/ginresp"
	"blackforestbytes.com/simplecloudnotifier/db"
	"blackforestbytes.com/simplecloudnotifier/models"
	"context"
	"errors"
	"github.com/gin-gonic/gin"
	"github.com/rs/zerolog/log"
	"gogs.mikescher.com/BlackForestBytes/goext/ginext"
	"gogs.mikescher.com/BlackForestBytes/goext/sq"
	"time"
)

type TxContext interface {
	Deadline() (deadline time.Time, ok bool)
	Done() <-chan struct{}
	Err() error
	Value(key any) any

	GetOrCreateTransaction(db db.DatabaseImpl) (sq.Tx, error)
}

type AppContext struct {
	app         *Application
	inner       context.Context
	cancelFunc  context.CancelFunc
	cancelled   bool
	transaction sq.Tx
	permissions models.PermissionSet
	ginContext  *gin.Context
}

func CreateAppContext(app *Application, g *gin.Context, innerCtx context.Context, cancelFn context.CancelFunc) *AppContext {
	return &AppContext{
		app:         app,
		inner:       innerCtx,
		cancelFunc:  cancelFn,
		cancelled:   false,
		transaction: nil,
		permissions: models.NewEmptyPermissions(),
		ginContext:  g,
	}
}

func (ac *AppContext) Deadline() (deadline time.Time, ok bool) {
	return ac.inner.Deadline()
}

func (ac *AppContext) Done() <-chan struct{} {
	return ac.inner.Done()
}

func (ac *AppContext) Err() error {
	return ac.inner.Err()
}

func (ac *AppContext) Value(key any) any {
	return ac.inner.Value(key)
}

func (ac *AppContext) Cancel() {
	ac.cancelled = true
	if ac.transaction != nil {
		log.Error().Str("uri", ac.RequestURI()).Msg("Rollback transaction (ctx-cancel)")
		err := ac.transaction.Rollback()
		if err != nil {
			log.Err(err).Stack().Msg("Failed to rollback transaction")
		}
		ac.transaction = nil
	}

	if ac.cancelFunc != nil {
		ac.cancelFunc()
	}
}

func (ac *AppContext) RequestURI() string {
	if ac.ginContext != nil && ac.ginContext.Request != nil {
		return ac.ginContext.Request.Method + " :: " + ac.ginContext.Request.RequestURI
	} else {
		return ""
	}
}

func (ac *AppContext) _FinishSuccess(res ginext.HTTPResponse) ginext.HTTPResponse {
	if ac.cancelled {
		panic("Cannot finish a cancelled request")
	}
	if ac.transaction != nil {
		err := ac.transaction.Commit()
		if err != nil {
			return ginresp.APIError(ac.ginContext, 500, apierr.COMMIT_FAILED, "Failed to comit changes to DB", err)
		}
		ac.transaction = nil
	}
	return res
}

func (ac *AppContext) GetOrCreateTransaction(db db.DatabaseImpl) (sq.Tx, error) {
	if ac.cancelled {
		return nil, errors.New("context cancelled")
	}
	if ac.transaction != nil {
		return ac.transaction, nil
	}
	tx, err := db.BeginTx(ac)
	if err != nil {
		return nil, err
	}
	ac.transaction = tx
	return tx, nil
}
