package auditlog

import (
	"context"
	"encoding/json"
	"time"

	"code.justin.tv/eventbus/controlplane/internal/db"
	"code.justin.tv/eventbus/controlplane/internal/ldap"
	"code.justin.tv/eventbus/controlplane/internal/logger"

	"github.com/jmoiron/sqlx"
	"github.com/jmoiron/sqlx/types"
	"github.com/pkg/errors"
	"go.uber.org/zap"
)

type BaseLog struct {
	ResourceName string `json:"resource_name" db:"resource_name"`
	ServiceID    int    `json:"service_id" db:"service_id"`
	DB           db.DB
}

const successMsg = "Success"

const APICreate = "CREATE"
const APIDelete = "DELETE"
const APIUpdate = "UPDATE"

const ResourceService = "SERVICE"
const ResourceSubscriptionTarget = "SUBSCRIPTION TARGET"
const ResourceSubscription = "SUBSCRIPTION"
const ResourceIAMRole = "IAM ROLE"
const ResourceAccount = "ACCOUNT"

func (b BaseLog) build(ctx context.Context, err error, resourceType string, action string, before interface{}, after interface{}) (*db.AuditLog, error) {
	al := &db.AuditLog{
		ServiceID:    b.ServiceID,
		UserName:     ldap.User(ctx),
		ResourceName: b.ResourceName,
		ResourceType: resourceType,
		Action:       action,
		Timestamp:    time.Now(),
	}
	if err == nil {
		al.Result = successMsg
	} else {
		al.Result = err.Error()
	}

	if before != nil {
		bytes, err := json.Marshal(before)
		if err != nil {
			return nil, errors.New("could not marshal before to JSON")
		}
		al.Before = types.JSONText(string(bytes))
	}

	if after != nil {
		bytes, err := json.Marshal(after)
		if err != nil {
			return nil, errors.New("could not marshal after to JSON")
		}
		al.After = types.JSONText(string(bytes))
	}

	return al, nil
}

func (b BaseLog) log(ctx context.Context, err error, resourceType string, action string, after interface{}) {
	log := logger.FromContext(ctx)
	al, err := b.build(ctx, err, resourceType, action, nil, after)
	if err != nil {
		log.Error("could not build audit log", zap.Error(err))
		return
	}

	_, err = b.DB.AuditLogCreate(ctx, al)
	if err != nil {
		log.Error("Could not populate audit log table", zap.Object("auditLog", al), zap.Error(err))
	}
}

func (b BaseLog) logTx(ctx context.Context, tx *sqlx.Tx, err error, resourceType string, action string, before interface{}, after interface{}) {
	log := logger.FromContext(ctx)
	al, err := b.build(ctx, err, resourceType, action, before, after)
	if err != nil {
		log.Error("could not build audit log", zap.Error(err))
		return
	}

	_, err = b.DB.AuditLogCreateTx(ctx, tx, al)
	if err != nil {
		log.Error("Could not populate audit log table", zap.Object("auditLog", al), zap.Error(err))
	}
}
