// Structs to temporarily hold audit data

package models

import (
	"encoding/json"
	"strconv"
	"time"

	"code.justin.tv/foundation/history-service/report"
)

const (
	MAX_UUID_LENGTH = 36
	MAX_DESCRIPTION = 1024

	ISO8601_NANO_FORMAT   = "2006-01-02T15:04:05.000000000Z07:00"
	ISO8601_MICRO_FORMAT  = "2006-01-02T15:04:05.000000Z07:00"
	ISO8601_MILLI_FORMAT  = "2006-01-02T15:04:05.000Z07:00"
	ISO8601_SECOND_FORMAT = "2006-01-02T15:04:05Z07:00"
)

type Audit struct {
	Uuid          string      `json:"uuid"`
	Action        string      `json:"action"`
	UserType      string      `json:"user_type"`
	UserId        string      `json:"user_id"`
	ResourceType  string      `json:"resource_type"`
	ResourceId    string      `json:"resource_id"`
	Description   string      `json:"description"`
	CreatedAt     string      `json:"created_at"`
	ExpiredAt     string      `json:"expired_at,omitempty"`
	ExpiredAtTime time.Time   `json:"-"`
	CreatedAtTime time.Time   `json:"-"`
	Expiry        int64       `json:"expiry,omitempty"`
	Changes       []ChangeSet `json:"changes"`
}

// JSON representation of the audit record
func (audit *Audit) ToJson() (string, error) {
	bolB, err := json.Marshal(audit)

	if err != nil {
		return "", err
	}

	return string(bolB), err
}

// Retrieve & populate audit by parsing JSON body
func (audit *Audit) FromJson(body string) error {
	err := json.Unmarshal([]byte(body), audit)

	if err != nil {
		err = report.NewBadRequestCustomError("Json error: " + err.Error())
		return err
	}

	err = audit.Validate()

	return err
}

// Need to support parsing different fractional seconds being sent by clients
func (audit *Audit) parseCreatedAt() (time.Time, error) {
	t, err := time.Parse(ISO8601_NANO_FORMAT, audit.CreatedAt)

	if err != nil {
		t, err = time.Parse(ISO8601_MILLI_FORMAT, audit.CreatedAt)
	}

	if err != nil {
		t, err = time.Parse(ISO8601_MICRO_FORMAT, audit.CreatedAt)
	}

	if err != nil {
		t, err = time.Parse(ISO8601_SECOND_FORMAT, audit.CreatedAt)
	}

	if err != nil {
		return time.Time{}, report.NewBadRequestCustomError("invalid format for creation time")
	}

	return t, nil
}

// Validates the audit record & returns error on validation failure
func (audit *Audit) Validate() error {
	if audit.CreatedAt == "" {
		audit.CreatedAt = time.Now().UTC().Format(ISO8601_NANO_FORMAT)
	}

	t, err := audit.parseCreatedAt()

	if err != nil {
		return err
	}

	audit.CreatedAtTime = t
	audit.CreatedAt = audit.CreatedAtTime.UTC().Format(ISO8601_NANO_FORMAT) // Ensuring that the time is always stored in UTC

	if audit.CreatedAtTime.Unix() <= 0 {
		return report.NewBadRequestCustomError("invalid creation time")
	}

	if audit.Expiry <= 0 {
		return report.NewBadRequestCustomError("expiry should be a positive integer")
	}

	audit.ExpiredAtTime = audit.CreatedAtTime.Add(time.Duration(audit.Expiry) * time.Second)
	audit.ExpiredAt = audit.ExpiredAtTime.Format(ISO8601_NANO_FORMAT)

	if len(audit.Uuid) != MAX_UUID_LENGTH {
		return report.NewBadRequestCustomError("uuid cannot be more than " + strconv.Itoa(MAX_UUID_LENGTH) + " characters long")
	}

	if audit.Action == "" {
		return report.NewBadRequestCustomError("action cannot be empty")
	}

	if audit.UserType == "" {
		return report.NewBadRequestCustomError("user_type cannot be empty")
	}

	if audit.UserId == "" {
		return report.NewBadRequestCustomError("user_id cannot be empty")
	}

	if len(audit.Description) > MAX_DESCRIPTION {
		return report.NewBadRequestCustomError("description cannot be more than " + strconv.Itoa(MAX_DESCRIPTION) + " characters long")
	}

	for _, change := range audit.Changes {
		if change.Attribute == "" {
			return report.NewBadRequestCustomError("attribute cannot be empty for changes")
		}
	}

	return nil
}
