package transfercoderepo

import (
	"database/sql"
	"errors"

	"github.com/jmoiron/sqlx"

	"github.com/oidc-mytoken/server/internal/db"
	"github.com/oidc-mytoken/server/internal/utils/hashUtils"
	"github.com/oidc-mytoken/server/shared/mytoken/pkg/mtid"
	"github.com/oidc-mytoken/server/shared/utils"
	"github.com/oidc-mytoken/server/shared/utils/cryptUtils"
)

// proxyToken holds information for proxy tokens, i.e. tokens that proxy another token, e.g. a short token
type proxyToken struct {
	id    string
	token string
	mtID  mtid.MTID

	encryptedJWT string
	decryptedJWT string
}

// newProxyToken creates a new proxyToken of the given length
func newProxyToken(size int) *proxyToken {
	token := utils.RandASCIIString(size)
	return createProxyToken(token)
}

func createProxyToken(token string) *proxyToken {
	id := hashUtils.SHA512Str([]byte(token))
	return &proxyToken{
		id:    id,
		token: token,
	}
}

// parseProxyToken parses the proxy token string into a proxyToken
func parseProxyToken(token string) *proxyToken {
	var id string
	if token != "" {
		id = hashUtils.SHA512Str([]byte(token))
	}
	return &proxyToken{
		id:    id,
		token: token,
	}
}

func (pt proxyToken) String() string {
	return pt.Token()
}

// Token returns the token of this proxyToken
func (pt proxyToken) Token() string {
	return pt.token
}

// ID returns the id of this token
func (pt *proxyToken) ID() string {
	if pt.id == "" {
		pt.id = hashUtils.SHA512Str([]byte(pt.token))
	}
	return pt.id
}

// SetJWT sets the jwt for this proxyToken
func (pt *proxyToken) SetJWT(jwt string, mID mtid.MTID) (err error) {
	pt.mtID = mID
	pt.decryptedJWT = jwt
	pt.encryptedJWT, err = cryptUtils.AES256Encrypt(jwt, pt.token)
	return
}

// JWT returns the decrypted jwt that is linked to this proxyToken
func (pt *proxyToken) JWT(tx *sqlx.Tx) (jwt string, valid bool, err error) {
	jwt = pt.decryptedJWT
	if jwt != "" {
		valid = true
		return
	}
	if pt.encryptedJWT == "" {
		if err = db.RunWithinTransaction(tx, func(tx *sqlx.Tx) error {
			var res struct {
				JWT  string    `db:"jwt"`
				MTID mtid.MTID `db:"MT_id"`
			}
			if err := tx.Get(&res, `SELECT jwt, MT_id FROM ProxyTokens WHERE id=?`, pt.id); err != nil {
				return err
			}
			pt.encryptedJWT = res.JWT
			pt.mtID = res.MTID
			return nil
		}); err != nil {
			if errors.Is(err, sql.ErrNoRows) {
				err = nil
				return
			}
			return
		}
	}
	valid = true
	if pt.encryptedJWT == "" {
		return
	}
	jwt, err = cryptUtils.AES256Decrypt(pt.encryptedJWT, pt.token)
	pt.decryptedJWT = jwt
	return
}

// Store stores the proxyToken
func (pt proxyToken) Store(tx *sqlx.Tx) error {
	return db.RunWithinTransaction(tx, func(tx *sqlx.Tx) error {
		_, err := tx.Exec(`INSERT INTO ProxyTokens (id, jwt, MT_id) VALUES (?,?,?)`, pt.id, pt.encryptedJWT, pt.mtID)
		return err
	})
}

// Update updates the jwt of the proxyToken
func (pt proxyToken) Update(tx *sqlx.Tx) error {
	return db.RunWithinTransaction(tx, func(tx *sqlx.Tx) error {
		_, err := tx.Exec(`UPDATE ProxyTokens SET jwt=?, MT_id=? WHERE id=?`, pt.encryptedJWT, pt.mtID, pt.id)
		return err
	})
}

// Delete deletes the proxyToken from the database, it does not delete the linked Mytoken, the jwt should have been retrieved earlier and the Mytoken if desired be revoked separately
func (pt proxyToken) Delete(tx *sqlx.Tx) error {
	return db.RunWithinTransaction(tx, func(tx *sqlx.Tx) error {
		_, err := tx.Exec(`DELETE FROM ProxyTokens WHERE id=?`, pt.id)
		return err
	})
}