package mapping

import (
	"context"

	semantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/analyticsauthoringv1"
	commonsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/commonv1"
	"github.com/pingcap-inc/nexuscrm-contract/wire-go/envelope"
	runtimev1 "github.com/pingcap-inc/nexuscrm-contract/wire-go/gen/runtime/v1"
	"google.golang.org/protobuf/proto"
)

func ToWireMutateAnalyticsDefinitionRequest(ctx context.Context, request semantic.Request) (*runtimev1.MutateAnalyticsDefinitionRequest, error) {
	if err := requirePlatformSemanticShape(semantic.CapabilityName); err != nil {
		return nil, err
	}
	if err := semantic.ValidateRequest(ctx, request); err != nil {
		return nil, err
	}
	operation, ok := toWireAnalyticsAuthoringOperation(request.Operation)
	if !ok {
		return nil, invalidPlatformWire()
	}
	result := &runtimev1.MutateAnalyticsDefinitionRequest{
		CommandId: request.CommandID, Operation: operation, ResourceId: request.ResourceID,
		Dashboard: toWireAnalyticsDashboardDefinition(request.Dashboard),
		Report:    toWireAnalyticsReportDefinition(request.Report),
		Folder:    toWireAnalyticsFolderDefinition(request.Folder), MinCatalogRevision: request.MinCatalogRevision,
	}
	if proto.Size(result) > envelope.MaxRequestWireBytes {
		return nil, analyticsAuthoringWireFrameFailure()
	}
	return result, nil
}

func FromWireMutateAnalyticsDefinitionRequest(ctx context.Context, request *runtimev1.MutateAnalyticsDefinitionRequest) (semantic.Request, error) {
	if err := requirePlatformSemanticShape(semantic.CapabilityName); err != nil {
		return semantic.Request{}, err
	}
	if request == nil {
		return semantic.Request{}, invalidPlatformWire()
	}
	operation, ok := fromWireAnalyticsAuthoringOperation(request.GetOperation())
	if !ok {
		return semantic.Request{}, invalidPlatformWire()
	}
	if err := commonsemantic.ValidateRequestContext(ctx); err != nil {
		return semantic.Request{}, err
	}
	if err := preflightWireAnalyticsAuthoringRequest(request); err != nil {
		return semantic.Request{}, err
	}
	dashboard := fromWireAnalyticsDashboardDefinition(request.GetDashboard())
	report, ok := fromWireAnalyticsReportDefinition(request.GetReport())
	if !ok {
		return semantic.Request{}, invalidPlatformWire()
	}
	folder, ok := fromWireAnalyticsFolderDefinition(request.GetFolder())
	if !ok {
		return semantic.Request{}, invalidPlatformWire()
	}
	result := semantic.Request{CommandID: request.GetCommandId(), Operation: operation, ResourceID: request.GetResourceId(), Dashboard: dashboard, Report: report, Folder: folder, MinCatalogRevision: request.MinCatalogRevision}
	if err := semantic.ValidateRequest(ctx, result); err != nil {
		return semantic.Request{}, err
	}
	return result, nil
}

func preflightWireAnalyticsAuthoringRequest(request *runtimev1.MutateAnalyticsDefinitionRequest) error {
	componentSet := request.GetDashboard().GetComponentSet()
	if componentSet != nil && len(componentSet.GetComponents()) > semantic.MaxDashboardComponents {
		return platformLimitFailure(commonsemantic.LimitOperationCount, semantic.MaxDashboardComponents)
	}
	if !wireReportDefinitionWithinEntryLimit(request.GetReport()) {
		return platformLimitFailure(commonsemantic.LimitOperationCount, semantic.MaxReportDefinitionEntries)
	}
	definitionBudget := newWireStringBudget(semantic.MaxDefinitionBytes)
	semanticBudget := newWireStringBudget(semantic.MaxSemanticRequestBytes)
	if !semanticBudget.take(request.GetCommandId(), request.GetResourceId()) {
		return platformLimitFailure(commonsemantic.LimitSemanticRequest, semantic.MaxSemanticRequestBytes)
	}
	takeDefinition := func(values ...string) error {
		if !definitionBudget.take(values...) {
			return platformLimitFailure(commonsemantic.LimitSemanticRequest, semantic.MaxDefinitionBytes)
		}
		if !semanticBudget.take(values...) {
			return platformLimitFailure(commonsemantic.LimitSemanticRequest, semantic.MaxSemanticRequestBytes)
		}
		return nil
	}
	takeOptionalDefinition := func(value *string) error {
		if value == nil {
			return nil
		}
		return takeDefinition(*value)
	}
	if dashboard := request.GetDashboard(); dashboard != nil {
		for _, value := range []*string{
			dashboard.Name, dashboard.StableName, dashboard.FolderId,
			dashboard.Description, dashboard.LayoutKind, dashboard.Kind,
		} {
			if err := takeOptionalDefinition(value); err != nil {
				return err
			}
		}
		if componentSet != nil {
			for _, component := range componentSet.GetComponents() {
				if component == nil {
					return invalidPlatformWire()
				}
				if err := takeDefinition(component.GetReportId(), component.GetKind(), component.GetTitle(), component.GetCanonicalConfiguration()); err != nil {
					return err
				}
			}
		}
	}
	if report := request.GetReport(); report != nil {
		for _, value := range []*string{
			report.Name, report.StableName, report.FolderId, report.Description,
			report.SourceObject, report.SourceRef, report.DisplayMode, report.Scope,
		} {
			if err := takeOptionalDefinition(value); err != nil {
				return err
			}
		}
		if columns := report.GetColumnSet(); columns != nil {
			if err := takeDefinition(columns.GetColumns()...); err != nil {
				return err
			}
		}
		if filters := report.GetFilterSet(); filters != nil {
			for _, filter := range filters.GetFilters() {
				if filter == nil {
					return invalidPlatformWire()
				}
				if err := takeDefinition(filter.GetColumn(), filter.GetOperator(), filter.GetValue()); err != nil {
					return err
				}
			}
		}
		if grouping := report.GetGroupingSet(); grouping != nil {
			if err := takeDefinition(grouping.GetGrouping()...); err != nil {
				return err
			}
		}
		if dateFilter := report.GetDateFilter(); dateFilter != nil {
			if err := takeDefinition(dateFilter.GetColumn(), dateFilter.GetDurationValue(), dateFilter.GetStartDate(), dateFilter.GetEndDate()); err != nil {
				return err
			}
		}
	}
	if folder := request.GetFolder(); folder != nil {
		for _, value := range []*string{folder.Name, folder.StableName, folder.Description, folder.Kind, folder.ParentFolderId} {
			if err := takeOptionalDefinition(value); err != nil {
				return err
			}
		}
	}
	if proto.Size(request) > envelope.MaxRequestWireBytes {
		return analyticsAuthoringWireFrameFailure()
	}
	return nil
}

func wireReportDefinitionWithinEntryLimit(report *runtimev1.AnalyticsReportDefinition) bool {
	if report == nil {
		return true
	}
	remaining := semantic.MaxReportDefinitionEntries
	for _, count := range []int{
		len(report.GetColumnSet().GetColumns()),
		len(report.GetFilterSet().GetFilters()),
		len(report.GetGroupingSet().GetGrouping()),
	} {
		if count > remaining {
			return false
		}
		remaining -= count
	}
	return true
}

func preflightWireAnalyticsAuthoringResponse(response *runtimev1.MutateAnalyticsDefinitionResponse) error {
	budget := newWireStringBudget(semantic.MaxSemanticResponseBytes)
	if !budget.take(response.GetCommandId(), response.GetResourceId()) ||
		!budget.takeValidatedReceiptBytes(response.GetReceipts()) || proto.Size(response) > envelope.MaxResponseWireBytes {
		return internalPlatformWire()
	}
	return nil
}

func toWireAnalyticsReportDefinition(value *semantic.ReportDefinition) *runtimev1.AnalyticsReportDefinition {
	if value == nil {
		return nil
	}
	result := &runtimev1.AnalyticsReportDefinition{
		Name: value.Name, StableName: value.StableName, FolderId: value.FolderID,
		Description: value.Description, SourceObject: value.SourceObject, SourceRef: value.SourceRef,
		DisplayMode: value.DisplayMode, Scope: value.Scope,
	}
	if value.Columns != nil {
		result.ColumnSet = &runtimev1.AnalyticsReportColumnSet{Columns: append([]string(nil), (*value.Columns)...)}
	}
	if value.Filters != nil {
		filters := make([]*runtimev1.AnalyticsReportFilter, 0, len(*value.Filters))
		for _, filter := range *value.Filters {
			filters = append(filters, &runtimev1.AnalyticsReportFilter{Column: filter.Column, Operator: filter.Operator, Value: filter.Value})
		}
		result.FilterSet = &runtimev1.AnalyticsReportFilterSet{Filters: filters}
	}
	if value.Grouping != nil {
		result.GroupingSet = &runtimev1.AnalyticsReportGroupingSet{Grouping: append([]string(nil), (*value.Grouping)...)}
	}
	if value.DateFilter != nil {
		result.DateFilterChange = &runtimev1.AnalyticsReportDefinition_DateFilter{DateFilter: &runtimev1.AnalyticsReportDateFilter{Column: value.DateFilter.Column, DurationValue: value.DateFilter.DurationValue, StartDate: value.DateFilter.StartDate, EndDate: value.DateFilter.EndDate}}
	} else if value.ClearDateFilter {
		result.DateFilterChange = &runtimev1.AnalyticsReportDefinition_ClearDateFilter{ClearDateFilter: &runtimev1.AnalyticsReportDateFilterClear{}}
	}
	return result
}

func fromWireAnalyticsReportDefinition(value *runtimev1.AnalyticsReportDefinition) (*semantic.ReportDefinition, bool) {
	if value == nil {
		return nil, true
	}
	result := &semantic.ReportDefinition{
		Name: value.Name, StableName: value.StableName, FolderID: value.FolderId,
		Description: value.Description, SourceObject: value.SourceObject, SourceRef: value.SourceRef,
		DisplayMode: value.DisplayMode, Scope: value.Scope,
	}
	if value.ColumnSet != nil {
		columns := make([]string, len(value.ColumnSet.Columns))
		copy(columns, value.ColumnSet.Columns)
		result.Columns = &columns
	}
	if value.FilterSet != nil {
		filters := make([]semantic.ReportFilter, 0, len(value.FilterSet.Filters))
		for _, filter := range value.FilterSet.Filters {
			if filter == nil {
				filters = append(filters, semantic.ReportFilter{})
				continue
			}
			filters = append(filters, semantic.ReportFilter{Column: filter.GetColumn(), Operator: filter.GetOperator(), Value: filter.GetValue()})
		}
		result.Filters = &filters
	}
	if value.GroupingSet != nil {
		grouping := make([]string, len(value.GroupingSet.Grouping))
		copy(grouping, value.GroupingSet.Grouping)
		result.Grouping = &grouping
	}
	switch change := value.GetDateFilterChange().(type) {
	case nil:
	case *runtimev1.AnalyticsReportDefinition_DateFilter:
		if change.DateFilter == nil {
			return nil, false
		}
		result.DateFilter = &semantic.ReportDateFilter{Column: change.DateFilter.GetColumn(), DurationValue: change.DateFilter.GetDurationValue(), StartDate: change.DateFilter.GetStartDate(), EndDate: change.DateFilter.GetEndDate()}
	case *runtimev1.AnalyticsReportDefinition_ClearDateFilter:
		if change.ClearDateFilter == nil {
			return nil, false
		}
		result.ClearDateFilter = true
	default:
		return nil, false
	}
	return result, true
}

func toWireAnalyticsDashboardDefinition(value *semantic.DashboardDefinition) *runtimev1.AnalyticsDashboardDefinition {
	if value == nil {
		return nil
	}
	result := &runtimev1.AnalyticsDashboardDefinition{
		Name: value.Name, StableName: value.StableName, FolderId: value.FolderID,
		Description: value.Description, LayoutKind: value.LayoutKind, Kind: value.Kind,
	}
	if value.Components != nil {
		components := make([]*runtimev1.AnalyticsDashboardComponent, 0, len(*value.Components))
		for _, component := range *value.Components {
			components = append(components, &runtimev1.AnalyticsDashboardComponent{
				ReportId: component.ReportID, Kind: component.Kind, Title: component.Title,
				CanonicalConfiguration: component.Configuration,
			})
		}
		result.ComponentSet = &runtimev1.AnalyticsDashboardComponentSet{Components: components}
	}
	return result
}

func fromWireAnalyticsDashboardDefinition(value *runtimev1.AnalyticsDashboardDefinition) *semantic.DashboardDefinition {
	if value == nil {
		return nil
	}
	result := &semantic.DashboardDefinition{
		Name: value.Name, StableName: value.StableName, FolderID: value.FolderId,
		Description: value.Description, LayoutKind: value.LayoutKind, Kind: value.Kind,
	}
	if value.ComponentSet != nil {
		components := make([]semantic.DashboardComponent, 0, len(value.ComponentSet.Components))
		for _, component := range value.ComponentSet.Components {
			if component == nil {
				components = append(components, semantic.DashboardComponent{})
				continue
			}
			components = append(components, semantic.DashboardComponent{
				ReportID: component.GetReportId(), Kind: component.GetKind(), Title: component.GetTitle(),
				Configuration: component.GetCanonicalConfiguration(),
			})
		}
		result.Components = &components
	}
	return result
}

func toWireAnalyticsFolderDefinition(value *semantic.FolderDefinition) *runtimev1.AnalyticsFolderDefinition {
	if value == nil {
		return nil
	}
	result := &runtimev1.AnalyticsFolderDefinition{
		Name: value.Name, StableName: value.StableName, Description: value.Description,
		Kind: value.Kind, ParentFolderId: value.ParentFolderID,
	}
	if value.Access != nil {
		access, _ := toWireAnalyticsFolderAccess(*value.Access)
		result.Access = &access
	}
	return result
}

func fromWireAnalyticsFolderDefinition(value *runtimev1.AnalyticsFolderDefinition) (*semantic.FolderDefinition, bool) {
	if value == nil {
		return nil, true
	}
	result := &semantic.FolderDefinition{
		Name: value.Name, StableName: value.StableName, Description: value.Description,
		Kind: value.Kind, ParentFolderID: value.ParentFolderId,
	}
	if value.Access != nil {
		access, ok := fromWireAnalyticsFolderAccess(*value.Access)
		if !ok {
			return nil, false
		}
		result.Access = &access
	}
	return result, true
}

func toWireAnalyticsFolderAccess(value semantic.FolderAccess) (runtimev1.AnalyticsFolderAccess, bool) {
	result, ok := map[semantic.FolderAccess]runtimev1.AnalyticsFolderAccess{
		semantic.FolderAccessHidden: runtimev1.AnalyticsFolderAccess_ANALYTICS_FOLDER_ACCESS_HIDDEN,
		semantic.FolderAccessShared: runtimev1.AnalyticsFolderAccess_ANALYTICS_FOLDER_ACCESS_SHARED,
		semantic.FolderAccessPublic: runtimev1.AnalyticsFolderAccess_ANALYTICS_FOLDER_ACCESS_PUBLIC,
	}[value]
	return result, ok
}

func fromWireAnalyticsFolderAccess(value runtimev1.AnalyticsFolderAccess) (semantic.FolderAccess, bool) {
	result, ok := map[runtimev1.AnalyticsFolderAccess]semantic.FolderAccess{
		runtimev1.AnalyticsFolderAccess_ANALYTICS_FOLDER_ACCESS_HIDDEN: semantic.FolderAccessHidden,
		runtimev1.AnalyticsFolderAccess_ANALYTICS_FOLDER_ACCESS_SHARED: semantic.FolderAccessShared,
		runtimev1.AnalyticsFolderAccess_ANALYTICS_FOLDER_ACCESS_PUBLIC: semantic.FolderAccessPublic,
	}[value]
	return result, ok
}

func analyticsAuthoringWireFrameFailure() *commonsemantic.Failure {
	return &commonsemantic.Failure{Category: commonsemantic.FailureResourceExhausted, LimitKind: commonsemantic.LimitWireFrame, Maximum: envelope.MaxRequestWireBytes}
}

func ToWireMutateAnalyticsDefinitionResponse(request semantic.Request, response semantic.Response) (*runtimev1.MutateAnalyticsDefinitionResponse, error) {
	if err := requirePlatformSemanticShape(semantic.CapabilityName); err != nil {
		return nil, err
	}
	if err := semantic.ValidateResponse(request, response); err != nil {
		return nil, err
	}
	return &runtimev1.MutateAnalyticsDefinitionResponse{CommandId: response.CommandID, ResourceId: response.ResourceID, Receipts: toWireReceipts(response.Receipts, platformCapability(semantic.CapabilityName))}, nil
}

func FromWireMutateAnalyticsDefinitionResponse(request semantic.Request, response *runtimev1.MutateAnalyticsDefinitionResponse) (semantic.Response, error) {
	if err := requirePlatformSemanticShape(semantic.CapabilityName); err != nil {
		return semantic.Response{}, err
	}
	if response == nil || response.GetReceipts() == nil {
		return semantic.Response{}, incompatibleFailureFor(platformCapability(semantic.CapabilityName))
	}
	receipts, err := fromWireReceipts(response.GetReceipts(), platformCapability(semantic.CapabilityName))
	if err != nil {
		return semantic.Response{}, err
	}
	if err := preflightWireAnalyticsAuthoringResponse(response); err != nil {
		return semantic.Response{}, err
	}
	result := semantic.Response{CommandID: response.GetCommandId(), ResourceID: response.GetResourceId(), Receipts: receipts}
	if err := semantic.ValidateResponse(request, result); err != nil {
		return semantic.Response{}, err
	}
	return result, nil
}

func toWireAnalyticsAuthoringOperation(value semantic.Operation) (runtimev1.AnalyticsAuthoringOperation, bool) {
	result, ok := map[semantic.Operation]runtimev1.AnalyticsAuthoringOperation{
		semantic.CreateDashboard: runtimev1.AnalyticsAuthoringOperation_ANALYTICS_AUTHORING_OPERATION_CREATE_DASHBOARD,
		semantic.UpdateDashboard: runtimev1.AnalyticsAuthoringOperation_ANALYTICS_AUTHORING_OPERATION_REPLACE_DASHBOARD_DEFINITION,
		semantic.CreateReport:    runtimev1.AnalyticsAuthoringOperation_ANALYTICS_AUTHORING_OPERATION_CREATE_REPORT_DEFINITION,
		semantic.UpdateReport:    runtimev1.AnalyticsAuthoringOperation_ANALYTICS_AUTHORING_OPERATION_UPDATE_REPORT_DEFINITION,
		semantic.DeleteReport:    runtimev1.AnalyticsAuthoringOperation_ANALYTICS_AUTHORING_OPERATION_DELETE_REPORT_DEFINITION,
		semantic.CreateFolder:    runtimev1.AnalyticsAuthoringOperation_ANALYTICS_AUTHORING_OPERATION_CREATE_FOLDER,
		semantic.UpdateFolder:    runtimev1.AnalyticsAuthoringOperation_ANALYTICS_AUTHORING_OPERATION_UPDATE_FOLDER,
		semantic.DeleteFolder:    runtimev1.AnalyticsAuthoringOperation_ANALYTICS_AUTHORING_OPERATION_DELETE_FOLDER,
	}[value]
	return result, ok
}

func fromWireAnalyticsAuthoringOperation(value runtimev1.AnalyticsAuthoringOperation) (semantic.Operation, bool) {
	result, ok := map[runtimev1.AnalyticsAuthoringOperation]semantic.Operation{
		runtimev1.AnalyticsAuthoringOperation_ANALYTICS_AUTHORING_OPERATION_CREATE_DASHBOARD:             semantic.CreateDashboard,
		runtimev1.AnalyticsAuthoringOperation_ANALYTICS_AUTHORING_OPERATION_REPLACE_DASHBOARD_DEFINITION: semantic.UpdateDashboard,
		runtimev1.AnalyticsAuthoringOperation_ANALYTICS_AUTHORING_OPERATION_CREATE_REPORT_DEFINITION:     semantic.CreateReport,
		runtimev1.AnalyticsAuthoringOperation_ANALYTICS_AUTHORING_OPERATION_UPDATE_REPORT_DEFINITION:     semantic.UpdateReport,
		runtimev1.AnalyticsAuthoringOperation_ANALYTICS_AUTHORING_OPERATION_DELETE_REPORT_DEFINITION:     semantic.DeleteReport,
		runtimev1.AnalyticsAuthoringOperation_ANALYTICS_AUTHORING_OPERATION_CREATE_FOLDER:                semantic.CreateFolder,
		runtimev1.AnalyticsAuthoringOperation_ANALYTICS_AUTHORING_OPERATION_UPDATE_FOLDER:                semantic.UpdateFolder,
		runtimev1.AnalyticsAuthoringOperation_ANALYTICS_AUTHORING_OPERATION_DELETE_FOLDER:                semantic.DeleteFolder,
	}[value]
	return result, ok
}
