// Copyright 2021 Molecula Corp. All rights reserved.
// package ctl contains all pilosa subcommands other than 'server'. These are
// generally administration, testing, and debugging tools.

package client

import (
	"encoding/json"
	"fmt"

	"github.com/molecula/featurebase/v3/pb"
)

// QueryResponse types.
const (
	QueryResultTypeNil uint32 = iota
	QueryResultTypeRow
	QueryResultTypePairs
	QueryResultTypePairsField
	QueryResultTypeValCount
	QueryResultTypeUint64
	QueryResultTypeBool
	QueryResultTypeRowIDs // this is not used by the client
	QueryResultTypeGroupCounts
	QueryResultTypeRowIdentifiers
	QueryResultTypePair
	QueryResultTypePairField
	QueryResultTypeSignedRow
)

// QueryResponse represents the response from a Pilosa query.
type QueryResponse struct {
	ResultList   []QueryResult `json:"results,omitempty"`
	ErrorMessage string        `json:"error-message,omitempty"`
	Success      bool          `json:"success,omitempty"`
}

func newQueryResponseFromInternal(response *pb.QueryResponse) (*QueryResponse, error) {
	if response.Err != "" {
		return &QueryResponse{
			ErrorMessage: response.Err,
			Success:      false,
		}, nil
	}
	results := make([]QueryResult, 0, len(response.Results))
	for _, r := range response.Results {
		result, err := newQueryResultFromInternal(r)
		if err != nil {
			return nil, err
		}
		results = append(results, result)
	}

	return &QueryResponse{
		ResultList: results,
		Success:    true,
	}, nil
}

// Results returns all results in the response.
func (qr *QueryResponse) Results() []QueryResult {
	return qr.ResultList
}

// Result returns the first result or nil.
func (qr *QueryResponse) Result() QueryResult {
	if len(qr.ResultList) == 0 {
		return nil
	}
	return qr.ResultList[0]
}

// QueryResult represents one of the results in the response.
type QueryResult interface {
	Type() uint32
	Row() RowResult
	CountItems() []CountResultItem
	CountItem() CountResultItem
	Count() int64
	Value() int64
	Changed() bool
	GroupCounts() []GroupCount
	RowIdentifiers() RowIdentifiersResult
}

func newQueryResultFromInternal(result *pb.QueryResult) (QueryResult, error) {
	switch result.Type {
	case QueryResultTypeNil:
		return NilResult{}, nil
	case QueryResultTypeRow:
		return newRowResultFromInternal(result.Row)
	case QueryResultTypePairs:
		return countItemsFromInternal(result.Pairs), nil
	case QueryResultTypePairsField:
		return countItemsFromInternal(result.PairsField.Pairs), nil
	case QueryResultTypeValCount:
		return &ValCountResult{
			Val: result.ValCount.Val,
			Cnt: result.ValCount.Count,
		}, nil
	case QueryResultTypeUint64:
		return IntResult(result.N), nil
	case QueryResultTypeBool:
		return BoolResult(result.Changed), nil
	case QueryResultTypeRowIdentifiers:
		return &RowIdentifiersResult{
			IDs:  result.RowIdentifiers.Rows,
			Keys: result.RowIdentifiers.Keys,
		}, nil
	case QueryResultTypeGroupCounts:
		return groupCountsFromInternal(result.GroupCounts), nil
	case QueryResultTypePair:
		return CountItem{CountResultItem: countItemFromInternal(result.Pairs[0])}, nil
	case QueryResultTypePairField:
		return CountItem{CountResultItem: countItemFromInternal(result.PairField.Pair)}, nil
	}

	return nil, ErrUnknownType
}

// CountResultItem represents a result from TopN call.
type CountResultItem struct {
	ID    uint64 `json:"id"`
	Key   string `json:"key,omitempty"`
	Count uint64 `json:"count"`
}

func (c *CountResultItem) String() string {
	if c.Key != "" {
		return fmt.Sprintf("%s:%d", c.Key, c.Count)
	}
	return fmt.Sprintf("%d:%d", c.ID, c.Count)
}

type CountItem struct {
	CountResultItem
}

// Type is the type of this result.
func (CountItem) Type() uint32 { return QueryResultTypePairField }

// Row returns a RowResult.
func (CountItem) Row() RowResult { return RowResult{} }

// CountItems returns a CountResultItem slice.
func (t CountItem) CountItems() []CountResultItem { return []CountResultItem{t.CountResultItem} }

// CountItem returns a CountResultItem
func (t CountItem) CountItem() CountResultItem { return t.CountResultItem }

// Count returns the result of a Count call.
func (CountItem) Count() int64 { return 0 }

// Value returns the result of a Min, Max or Sum call.
func (CountItem) Value() int64 { return 0 }

// Changed returns whether the corresponding Set or Clear call changed the value of a bit.
func (CountItem) Changed() bool { return false }

// GroupCounts returns the result of a GroupBy call.
func (CountItem) GroupCounts() []GroupCount { return nil }

// RowIdentifiers returns the result of a Rows call.
func (CountItem) RowIdentifiers() RowIdentifiersResult { return RowIdentifiersResult{} }

func countItemFromInternal(item *pb.Pair) CountResultItem {
	return CountResultItem{ID: item.ID, Key: item.Key, Count: item.Count}
}

func countItemsFromInternal(items []*pb.Pair) TopNResult {
	result := make([]CountResultItem, 0, len(items))
	for _, v := range items {
		result = append(result, countItemFromInternal(v))
	}
	return TopNResult(result)
}

// TopNResult is returned from TopN call.
type TopNResult []CountResultItem

// Type is the type of this result.
func (TopNResult) Type() uint32 { return QueryResultTypePairsField }

// Row returns a RowResult.
func (TopNResult) Row() RowResult { return RowResult{} }

// CountItems returns a CountResultItem slice.
func (t TopNResult) CountItems() []CountResultItem { return t }

// CountItem returns a CountResultItem
func (t TopNResult) CountItem() CountResultItem {
	if len(t) >= 1 {
		return t[0]
	}
	return CountResultItem{}
}

// Count returns the result of a Count call.
func (TopNResult) Count() int64 { return 0 }

// Value returns the result of a Min, Max or Sum call.
func (TopNResult) Value() int64 { return 0 }

// Changed returns whether the corresponding Set or Clear call changed the value of a bit.
func (TopNResult) Changed() bool { return false }

// GroupCounts returns the result of a GroupBy call.
func (TopNResult) GroupCounts() []GroupCount { return nil }

// RowIdentifiers returns the result of a Rows call.
func (TopNResult) RowIdentifiers() RowIdentifiersResult { return RowIdentifiersResult{} }

// RowResult represents a result from Row, Union, Intersect, Difference and Range PQL calls.
type RowResult struct {
	Columns []uint64 `json:"columns"`
	Keys    []string `json:"keys"`
}

func newRowResultFromInternal(row *pb.Row) (*RowResult, error) {
	return &RowResult{
		Columns: row.Columns,
		Keys:    row.Keys,
	}, nil
}

// Type is the type of this result.
func (RowResult) Type() uint32 { return QueryResultTypeRow }

// Row returns a RowResult.
func (b RowResult) Row() RowResult { return b }

// CountItems returns a CountResultItem slice.
func (RowResult) CountItems() []CountResultItem { return nil }

// CountItem returns a CountResultItem
func (RowResult) CountItem() CountResultItem { return CountResultItem{} }

// Count returns the result of a Count call.
func (RowResult) Count() int64 { return 0 }

// Value returns the result of a Min, Max or Sum call.
func (RowResult) Value() int64 { return 0 }

// Changed returns whether the corresponding Set or Clear call changed the value of a bit.
func (RowResult) Changed() bool { return false }

// GroupCounts returns the result of a GroupBy call.
func (RowResult) GroupCounts() []GroupCount { return nil }

// RowIdentifiers returns the result of a Rows call.
func (RowResult) RowIdentifiers() RowIdentifiersResult { return RowIdentifiersResult{} }

// MarshalJSON serializes this row result.
func (b RowResult) MarshalJSON() ([]byte, error) {
	columns := b.Columns
	if columns == nil {
		columns = []uint64{}
	}
	keys := b.Keys
	if keys == nil {
		keys = []string{}
	}
	return json.Marshal(struct {
		Columns []uint64 `json:"columns"`
		Keys    []string `json:"keys"`
	}{
		Columns: columns,
		Keys:    keys,
	})
}

// ValCountResult is returned from Min, Max and Sum calls.
type ValCountResult struct {
	Val int64 `json:"val"`
	Cnt int64 `json:"count"`
}

// Type is the type of this result.
func (ValCountResult) Type() uint32 { return QueryResultTypeValCount }

// Row returns a RowResult.
func (ValCountResult) Row() RowResult { return RowResult{} }

// CountItems returns a CountResultItem slice.
func (ValCountResult) CountItems() []CountResultItem { return nil }

// CountItem returns a CountResultItem
func (ValCountResult) CountItem() CountResultItem { return CountResultItem{} }

// Count returns the result of a Count call.
func (c ValCountResult) Count() int64 { return c.Cnt }

// Value returns the result of a Min, Max or Sum call.
func (c ValCountResult) Value() int64 { return c.Val }

// Changed returns whether the corresponding Set or Clear call changed the value of a bit.
func (ValCountResult) Changed() bool { return false }

// GroupCounts returns the result of a GroupBy call.
func (ValCountResult) GroupCounts() []GroupCount { return nil }

// RowIdentifiers returns the result of a Rows call.
func (ValCountResult) RowIdentifiers() RowIdentifiersResult { return RowIdentifiersResult{} }

// IntResult is returned from Count call.
type IntResult int64

// Type is the type of this result.
func (IntResult) Type() uint32 { return QueryResultTypeUint64 }

// Row returns a RowResult.
func (IntResult) Row() RowResult { return RowResult{} }

// CountItems returns a CountResultItem slice.
func (IntResult) CountItems() []CountResultItem { return nil }

// CountItem returns a CountResultItem
func (IntResult) CountItem() CountResultItem { return CountResultItem{} }

// Count returns the result of a Count call.
func (i IntResult) Count() int64 { return int64(i) }

// Value returns the result of a Min, Max or Sum call.
func (IntResult) Value() int64 { return 0 }

// Changed returns whether the corresponding Set or Clear call changed the value of a bit.
func (IntResult) Changed() bool { return false }

// GroupCounts returns the result of a GroupBy call.
func (IntResult) GroupCounts() []GroupCount { return nil }

// RowIdentifiers returns the result of a Rows call.
func (IntResult) RowIdentifiers() RowIdentifiersResult { return RowIdentifiersResult{} }

// BoolResult is returned from Set and Clear calls.
type BoolResult bool

// Type is the type of this result.
func (BoolResult) Type() uint32 { return QueryResultTypeBool }

// Row returns a RowResult.
func (BoolResult) Row() RowResult { return RowResult{} }

// CountItems returns a CountResultItem slice.
func (BoolResult) CountItems() []CountResultItem { return nil }

// CountItem returns a CountResultItem
func (BoolResult) CountItem() CountResultItem { return CountResultItem{} }

// Count returns the result of a Count call.
func (BoolResult) Count() int64 { return 0 }

// Value returns the result of a Min, Max or Sum call.
func (BoolResult) Value() int64 { return 0 }

// Changed returns whether the corresponding Set or Clear call changed the value of a bit.
func (b BoolResult) Changed() bool { return bool(b) }

// GroupCounts returns the result of a GroupBy call.
func (BoolResult) GroupCounts() []GroupCount { return nil }

// RowIdentifiers returns the result of a Rows call.
func (BoolResult) RowIdentifiers() RowIdentifiersResult { return RowIdentifiersResult{} }

// NilResult is returned from calls which don't return a value.
type NilResult struct{}

// Type is the type of this result.
func (NilResult) Type() uint32 { return QueryResultTypeNil }

// Row returns a RowResult.
func (NilResult) Row() RowResult { return RowResult{} }

// CountItems returns a CountResultItem slice.
func (NilResult) CountItems() []CountResultItem { return nil }

// CountItem returns a CountResultItem
func (NilResult) CountItem() CountResultItem { return CountResultItem{} }

// Count returns the result of a Count call.
func (NilResult) Count() int64 { return 0 }

// Value returns the result of a Min, Max or Sum call.
func (NilResult) Value() int64 { return 0 }

// Changed returns whether the corresponding Set or Clear call changed the value of a bit.
func (NilResult) Changed() bool { return false }

// GroupCounts returns the result of a GroupBy call.
func (NilResult) GroupCounts() []GroupCount { return nil }

// RowIdentifiers returns the result of a Rows call.
func (NilResult) RowIdentifiers() RowIdentifiersResult { return RowIdentifiersResult{} }

// FieldRow represents a Group in a GroupBy call result.
type FieldRow struct {
	FieldName string `json:"field"`
	RowID     uint64 `json:"rowID"`
	RowKey    string `json:"rowKey"`
	Value     *int64 `json:"value,omitempty"`
}

// GroupCount contains groups and their count in a GroupBy call result.
type GroupCount struct {
	Groups []FieldRow `json:"groups"`
	Count  int64      `json:"count"`
	Agg    int64      `json:"agg"`
}

// GroupCountResult is returned from GroupBy call.
type GroupCountResult []GroupCount

// Type is the type of this result.
func (GroupCountResult) Type() uint32 { return QueryResultTypeGroupCounts }

// Row returns a RowResult.
func (GroupCountResult) Row() RowResult { return RowResult{} }

// CountItems returns a CountResultItem slice.
func (GroupCountResult) CountItems() []CountResultItem { return nil }

// CountItem returns a CountResultItem
func (GroupCountResult) CountItem() CountResultItem { return CountResultItem{} }

// Count returns the result of a Count call.
func (GroupCountResult) Count() int64 { return 0 }

// Value returns the result of a Min, Max or Sum call.
func (GroupCountResult) Value() int64 { return 0 }

// Changed returns whether the corresponding Set or Clear call changed the value of a bit.
func (GroupCountResult) Changed() bool { return false }

// GroupCounts returns the result of a GroupBy call.
func (r GroupCountResult) GroupCounts() []GroupCount { return r }

// RowIdentifiers returns the result of a Rows call.
func (GroupCountResult) RowIdentifiers() RowIdentifiersResult { return RowIdentifiersResult{} }

// RowIdentifiersResult is returned from a Rows call.
type RowIdentifiersResult struct {
	IDs  []uint64 `json:"ids"`
	Keys []string `json:"keys,omitempty"`
}

// Type is the type of this result.
func (RowIdentifiersResult) Type() uint32 { return QueryResultTypeRowIdentifiers }

// Row returns a RowResult.
func (RowIdentifiersResult) Row() RowResult { return RowResult{} }

// CountItems returns a CountResultItem slice.
func (RowIdentifiersResult) CountItems() []CountResultItem { return nil }

// CountItem returns a CountResultItem
func (RowIdentifiersResult) CountItem() CountResultItem { return CountResultItem{} }

// Count returns the result of a Count call.
func (RowIdentifiersResult) Count() int64 { return 0 }

// Value returns the result of a Min, Max or Sum call.
func (RowIdentifiersResult) Value() int64 { return 0 }

// Changed returns whether the corresponding Set or Clear call changed the value of a bit.
func (RowIdentifiersResult) Changed() bool { return false }

// GroupCounts returns the result of a GroupBy call.
func (RowIdentifiersResult) GroupCounts() []GroupCount { return nil }

// RowIdentifiers returns the result of a Rows call.
func (r RowIdentifiersResult) RowIdentifiers() RowIdentifiersResult { return r }

func groupCountsFromInternal(items *pb.GroupCounts) GroupCountResult {
	result := make([]GroupCount, 0, len(items.Groups))
	for _, g := range items.Groups {
		groups := make([]FieldRow, 0, len(g.Group))
		for _, f := range g.Group {
			fr := FieldRow{
				FieldName: f.Field,
				RowID:     f.RowID,
				RowKey:    f.RowKey,
			}
			if f.Value != nil {
				fr.Value = &f.Value.Value
			}
			groups = append(groups, fr)
		}
		result = append(result, GroupCount{
			Groups: groups,
			Count:  int64(g.Count),
			Agg:    int64(g.Agg),
		})
	}
	return GroupCountResult(result)
}
