package repository

import (
	"context"
	"encoding/json"
	"fmt"
	"github.com/Masterminds/squirrel"
	"github.com/cortezaproject/corteza-server/pkg/actionlog"
	"github.com/cortezaproject/corteza-server/pkg/rh"
	"github.com/titpetric/factory"
	"time"
)

type (
	// Basic mysql storage backend for audit log events
	//
	// this does not follow the usual (one) repository pattern
	// but tries to move towards multi-flavoured repository support
	mysql struct {
		dbh *factory.DB
		tbl string
	}

	event struct {
		Timestamp     time.Time       `db:"ts"`
		RequestOrigin string          `db:"request_origin"`
		RequestID     string          `db:"request_id"`
		ActorIPAddr   string          `db:"actor_ip_addr"`
		ActorID       uint64          `db:"actor_id"`
		Resource      string          `db:"resource"`
		Action        string          `db:"action"`
		Error         string          `db:"error"`
		Severity      int             `db:"severity"`
		Description   string          `db:"description"`
		Meta          json.RawMessage `db:"meta"`
	}
)

func Mysql(db *factory.DB, tbl string) *mysql {
	return &mysql{
		// connection
		dbh: db,

		// table to store the data
		tbl: tbl,
	}
}

func (r *mysql) db() *factory.DB {
	return r.dbh
}

func (r mysql) columns() []string {
	return []string{
		"ts",
		"request_origin",
		"request_id",
		"actor_ip_addr",
		"actor_id",
		"resource",
		"action",
		"error",
		"severity",
		"description",
		"meta",
	}
}

func (r mysql) query() squirrel.SelectBuilder {
	return squirrel.
		Select(r.columns()...).
		From(r.tbl)
}

func (r *mysql) Find(ctx context.Context, flt actionlog.Filter) (set actionlog.ActionSet, f actionlog.Filter, err error) {
	f = flt

	query := r.query()

	if f.From != nil {
		query = query.Where(squirrel.GtOrEq{"ts": f.From})
	}

	if f.To != nil {
		query = query.Where(squirrel.LtOrEq{"ts": f.To})
	}

	if len(f.ActorID) > 0 {
		query = query.Where(squirrel.Eq{"actor_id": f.ActorID})
	}

	if f.Resource != "" {
		query = query.Where(squirrel.Eq{"resource": f.Resource})
	}

	if f.Action != "" {
		query = query.Where(squirrel.Eq{"action": f.Action})
	}

	// @todo implement filtering with query (via pkg/ql)

	query = query.OrderBy("ts DESC")

	results := make([]*event, 0)
	if err = rh.FetchPaged(r.db(), query, f.PageFilter, &results); err != nil {
		return nil, f, err
	}

	set = make(actionlog.ActionSet, len(results))
	for i, r := range results {
		set[i] = &actionlog.Action{
			Timestamp:     r.Timestamp,
			RequestOrigin: r.RequestOrigin,
			RequestID:     r.RequestID,
			ActorIPAddr:   r.ActorIPAddr,
			ActorID:       r.ActorID,
			Resource:      r.Resource,
			Action:        r.Action,
			Error:         r.Error,
			Severity:      actionlog.Severity(r.Severity),
			Description:   r.Description,
		}

		// ignore all unmarshaling issues
		_ = json.Unmarshal(r.Meta, &set[i].Meta)
	}

	return set, f, nil
}

// Record stores audit event
func (r *mysql) Record(ctx context.Context, e *actionlog.Action) error {
	m, err := json.Marshal(e.Meta)
	if err != nil {
		return fmt.Errorf("could not format auditlog event: %w", err)
	}

	return r.dbh.With(ctx).InsertIgnore(r.tbl, event{
		Timestamp:     e.Timestamp,
		RequestOrigin: e.RequestOrigin,
		RequestID:     e.RequestID,
		ActorIPAddr:   e.ActorIPAddr,
		ActorID:       e.ActorID,
		Resource:      e.Resource,
		Action:        e.Action,
		Error:         e.Error,
		Severity:      int(e.Severity),
		Description:   e.Description,
		Meta:          m,
	})
}