package service

import (
	"bytes"
	"encoding/json"
	"fmt"
	"strings"

	v1 "github.com/RafayLabs/rcloud-base/proto/rpc/audit"
	"google.golang.org/protobuf/types/known/structpb"
)

type AuditLogService struct {
	auditQuery *ElasticSearchQuery
}

func NewAuditLogService(url string, auditPattern string, logPrefix string) (*AuditLogService, error) {
	auditQuery, err := NewElasticSearchQuery(url, auditPattern, logPrefix)
	if err != nil {
		return nil, err
	}
	return &AuditLogService{auditQuery: auditQuery}, nil
}

func (a *AuditLogService) GetAuditLog(req *v1.AuditLogSearchRequest) (res *v1.AuditLogSearchResponse, err error) {
	if err != nil {
		return nil, err
	}
	projectID, err := getPrjectIdFromUrlScope(req.GetMetadata().UrlScope)
	if err != nil {
		return nil, err
	}
	req.Filter.ProjectIds = []string{projectID}
	return a.GetAuditLogByProjects(req)
}

func validateQueryString(queryString string) error {
	if strings.Contains(queryString, "*") {
		return fmt.Errorf("Sorry. '*' is not supported in search query")
	}
	if len(queryString) > 0 && len(queryString) < 3 {
		return fmt.Errorf("Search string has to be atleast 3 characters")
	}
	return nil
}

func getPrjectIdFromUrlScope(urlScope string) (string, error) {
	s := strings.Split(urlScope, "/")
	if len(s) != 2 {
		_log.Errorw("Unable to retrieve projectID from urlScope", "urlScope", urlScope)
		return "", fmt.Errorf("Unable to retrieve projectID from urlScope")
	}
	return s[1], nil
}

func (a *AuditLogService) GetAuditLogByProjects(req *v1.AuditLogSearchRequest) (res *v1.AuditLogSearchResponse, err error) {
	// No embedding in golang/protoc (https://github.com/golang/protobuf/issues/192)
	oid := req.GetMetadata().GetOrganization() // TODO: these are to be filled in by authinterceptor
	pid := req.GetMetadata().GetPartner()
	err = validateQueryString(req.GetFilter().QueryString)
	if err != nil {
		return nil, err
	}
	res = &v1.AuditLogSearchResponse{
		Result: &structpb.Struct{},
	}
	var buf bytes.Buffer
	var r map[string]interface{}
	query := map[string]interface{}{
		"_source": true,
		"size":    500,
		"query": map[string]interface{}{
			"bool": map[string]interface{}{
				"must": []map[string]interface{}{
					{
						"term": map[string]interface{}{
							"organization_id": oid,
						},
					},
					{
						"term": map[string]interface{}{
							"partner_id": pid,
						},
					},
					{
						"term": map[string]interface{}{
							"category": "AUDIT",
						},
					},
				},
			},
		},
		"sort": map[string]interface{}{
			"timestamp": map[string]interface{}{
				"order": "desc",
			},
		},
		"aggs": map[string]interface{}{
			"group_by_username": map[string]interface{}{
				"terms": map[string]interface{}{
					"field": "actor.account.username",
				},
			},
			"group_by_type": map[string]interface{}{
				"terms": map[string]interface{}{
					"field": "type",
				},
			},
		},
	}

	//aggregations
	agg, _ := query["aggs"].(map[string]interface{})
	if req.GetFilter().DashboardData {
		agg["group_by_project"] = map[string]interface{}{
			"terms": map[string]interface{}{
				"field": "project_id",
				"size":  1000,
			},
			"aggs": map[string]interface{}{
				"group_by_username": map[string]interface{}{
					"terms": map[string]interface{}{
						"field": "actor.account.username",
						"size":  1000,
					},
				},
				"group_by_type": map[string]interface{}{
					"terms": map[string]interface{}{
						"field": "type",
						"size":  1000,
					},
				},
			},
		}
	}

	// Filters
	q, _ := query["query"].(map[string]interface{})
	b, _ := q["bool"].(map[string]interface{})
	m, _ := b["must"].([]map[string]interface{})

	//Results not required in case of dashboard - only aggregations required
	if req.GetFilter().DashboardData {
		query["size"] = 0
	}
	//	Add timefrom filter
	if req.GetFilter().Timefrom != "" {
		b["filter"] = map[string]interface{}{
			"range": map[string]interface{}{
				"timestamp": map[string]interface{}{
					"gte": req.GetFilter().Timefrom,
					"lt":  "now",
				},
			},
		}
	}
	// Add type
	if req.GetFilter().Type != "" {
		t := map[string]interface{}{
			"term": map[string]interface{}{
				"type": req.GetFilter().Type,
			},
		}
		m = append(m, t)
	}
	// Add user
	if req.GetFilter().User != "" {
		t := map[string]interface{}{
			"term": map[string]interface{}{
				"actor.account.username": req.GetFilter().User,
			},
		}
		m = append(m, t)
	}
	// Add user
	if req.GetFilter().Client != "" {
		t := map[string]interface{}{
			"term": map[string]interface{}{
				"client.type": req.GetFilter().Client,
			},
		}
		m = append(m, t)
	}
	// ProjectIds
	if len(req.GetFilter().ProjectIds) > 0 {
		t := map[string]interface{}{
			"terms": map[string]interface{}{
				"project_id": req.GetFilter().ProjectIds,
			},
		}
		m = append(m, t)
	}
	// query string
	if req.GetFilter().QueryString != "" {
		q := map[string]interface{}{
			"query_string": map[string]interface{}{
				"query": req.GetFilter().QueryString,
			},
		}
		m = append(m, q)
	}
	b["must"] = m
	if err := json.NewEncoder(&buf).Encode(query); err != nil {
		_log.Errorw("Error encoding query:", " err", err)
		return res, err
	}
	_log.Debug("Executing Query: ", q)
	r, err = a.auditQuery.Handle(buf)
	if err != nil {
		return res, err
	}
	if r == nil {
		return res, nil
	}
	// raw, err := json.Marshal(r)
	// if err != nil {
	// 	return res, err
	// }
	raw, err := structpb.NewStruct(r)
	if err != nil {
		return res, err
	}
	res = &v1.AuditLogSearchResponse{Result: raw}
	return res, nil
}
