package approvalir

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
)

// The publication document is deliberately separate from the exported IR
// structs. Those structs predate revision-bound Catalog publication and their
// default JSON field names are an existing compatibility surface. The v1
// envelope owns this lower-camel canonical encoding without changing that
// surface.
type definitionEnvelopeDocument struct {
	Schema       string             `json:"schema"`
	SourceDigest string             `json:"sourceDigest"`
	Definition   definitionDocument `json:"definition"`
}

type definitionDocument struct {
	Label                    string                    `json:"label,omitempty"`
	Active                   bool                      `json:"active"`
	Description              string                    `json:"description,omitempty"`
	AllowRecall              bool                      `json:"allowRecall"`
	Policy                   approvalPolicyDocument    `json:"policy"`
	FinalApprovalRecordLock  bool                      `json:"finalApprovalRecordLock"`
	FinalRejectionRecordLock bool                      `json:"finalRejectionRecordLock"`
	EntryCriteria            *criteriaDocument         `json:"entryCriteria,omitempty"`
	ProcessOrder             *int                      `json:"processOrder,omitempty"`
	HierarchyRouting         *hierarchyRoutingDocument `json:"hierarchyRouting,omitempty"`
	Steps                    []stepDocument            `json:"steps"`
	InitialSubmissionActions []actionDocument          `json:"initialSubmissionActions"`
	StepApprovalActions      []actionDocument          `json:"stepApprovalActions"`
	FinalApprovalActions     []actionDocument          `json:"finalApprovalActions"`
	FinalRejectionActions    []actionDocument          `json:"finalRejectionActions"`
	RecallActions            []actionDocument          `json:"recallActions"`
	ReassignActions          []actionDocument          `json:"reassignActions"`
}

type approvalPolicyDocument struct {
	RecordEditability RecordEditability          `json:"recordEditability"`
	AllowedSubmitters []allowedSubmitterDocument `json:"allowedSubmitters"`
}

type allowedSubmitterDocument struct {
	Submitter string        `json:"submitter,omitempty"`
	Kind      SubmitterKind `json:"kind"`
}

type hierarchyRoutingDocument struct {
	UserHierarchyField string                    `json:"userHierarchyField"`
	InitialPrincipal   HierarchyInitialPrincipal `json:"initialPrincipal"`
}

type stepDocument struct {
	Name                 string               `json:"name"`
	Label                string               `json:"label"`
	Description          string               `json:"description,omitempty"`
	AllowDelegate        bool                 `json:"allowDelegate"`
	AllowComments        *bool                `json:"allowComments,omitempty"`
	RequireComments      *bool                `json:"requireComments,omitempty"`
	Approvers            []approverDocument   `json:"approvers"`
	MultipleApproverMode MultipleApproverMode `json:"multipleApproverMode,omitempty"`
	RejectBehavior       RejectBehavior       `json:"rejectBehavior"`
	Criteria             *criteriaDocument    `json:"criteria,omitempty"`
	IfCriteriaNotMet     IfCriteriaNotMet     `json:"ifCriteriaNotMet,omitempty"`
	ApprovalActions      []actionDocument     `json:"approvalActions"`
	RejectionActions     []actionDocument     `json:"rejectionActions"`
}

type approverDocument struct {
	Kind      ApproverKind `json:"kind"`
	Reference string       `json:"reference,omitempty"`
}

type criteriaDocument struct {
	Mode          CriteriaMode           `json:"mode,omitempty"`
	Formula       string                 `json:"formula,omitempty"`
	BooleanFilter string                 `json:"booleanFilter,omitempty"`
	Items         []criteriaItemDocument `json:"items"`
}

type criteriaItemDocument struct {
	Field     string           `json:"field"`
	Operation CriteriaOperator `json:"operation"`
	Value     string           `json:"value"`
}

type actionDocument struct {
	Name        string                     `json:"name"`
	Kind        ActionKind                 `json:"kind"`
	Reference   *actionReferenceDocument   `json:"reference,omitempty"`
	FieldUpdate *fieldUpdateActionDocument `json:"fieldUpdate,omitempty"`
	Flow        *flowActionDocument        `json:"flow,omitempty"`
	Alert       *alertActionDocument       `json:"alert,omitempty"`
}

type actionReferenceDocument struct {
	APIName string `json:"apiName"`
}

type fieldUpdateActionDocument struct {
	FieldAPIName string                   `json:"fieldApiName"`
	Value        fieldUpdateValueDocument `json:"value"`
}

type flowActionDocument struct {
	APIName string `json:"apiName"`
}

type alertActionDocument struct {
	EmailTemplateID string `json:"emailTemplateId"`
}

type fieldUpdateValueDocument struct {
	Kind       FieldUpdateValueKind `json:"kind"`
	Null       bool                 `json:"null"`
	SourceText *string              `json:"sourceText,omitempty"`
	String     *string              `json:"string,omitempty"`
	Boolean    *bool                `json:"boolean,omitempty"`
	Integer    *int64               `json:"integer,omitempty"`
	Decimal    *decimalDocument     `json:"decimal,omitempty"`
	Date       *string              `json:"date,omitempty"`
	DateTime   *string              `json:"dateTime,omitempty"`
	Reference  *string              `json:"reference,omitempty"`
}

type decimalDocument struct {
	Coefficient string `json:"coefficient"`
	Scale       uint32 `json:"scale"`
}

// MarshalJSON publishes only the versioned envelope encoding. It does not
// alter the historical JSON encoding of Definition or its nested public types.
func (e DefinitionEnvelope) MarshalJSON() ([]byte, error) {
	return json.Marshal(envelopeToDocument(e))
}

// UnmarshalJSON accepts only the closed v1 document grammar. Semantic and
// canonical validation remain DecodeDefinitionEnvelope's responsibility.
func (e *DefinitionEnvelope) UnmarshalJSON(data []byte) error {
	if e == nil {
		return fmt.Errorf("approval definition envelope is nil")
	}
	decoder := json.NewDecoder(bytes.NewReader(data))
	decoder.DisallowUnknownFields()
	var document definitionEnvelopeDocument
	if err := decoder.Decode(&document); err != nil {
		return err
	}
	if err := decoder.Decode(&struct{}{}); err != io.EOF {
		if err == nil {
			return fmt.Errorf("trailing JSON value")
		}
		return fmt.Errorf("trailing content: %w", err)
	}
	*e = envelopeFromDocument(document)
	return nil
}

func envelopeToDocument(envelope DefinitionEnvelope) definitionEnvelopeDocument {
	return definitionEnvelopeDocument{
		Schema:       envelope.Schema,
		SourceDigest: envelope.SourceDigest,
		Definition:   definitionToDocument(envelope.Definition),
	}
}

func envelopeFromDocument(document definitionEnvelopeDocument) DefinitionEnvelope {
	return DefinitionEnvelope{
		Schema:       document.Schema,
		SourceDigest: document.SourceDigest,
		Definition:   definitionFromDocument(document.Definition),
	}
}

func definitionToDocument(value Definition) definitionDocument {
	return definitionDocument{
		Label: value.Label, Active: value.Active, Description: value.Description, AllowRecall: value.AllowRecall,
		Policy: approvalPolicyToDocument(value.Policy), FinalApprovalRecordLock: value.FinalApprovalRecordLock,
		FinalRejectionRecordLock: value.FinalRejectionRecordLock, EntryCriteria: criteriaToDocument(value.EntryCriteria),
		ProcessOrder: value.ProcessOrder, HierarchyRouting: hierarchyRoutingToDocument(value.HierarchyRouting),
		Steps: stepListToDocument(value.Steps), InitialSubmissionActions: actionListToDocument(value.InitialSubmissionActions),
		StepApprovalActions: actionListToDocument(value.StepApprovalActions), FinalApprovalActions: actionListToDocument(value.FinalApprovalActions),
		FinalRejectionActions: actionListToDocument(value.FinalRejectionActions), RecallActions: actionListToDocument(value.RecallActions),
		ReassignActions: actionListToDocument(value.ReassignActions),
	}
}

func definitionFromDocument(value definitionDocument) Definition {
	return Definition{
		Label: value.Label, Active: value.Active, Description: value.Description, AllowRecall: value.AllowRecall,
		Policy: approvalPolicyFromDocument(value.Policy), FinalApprovalRecordLock: value.FinalApprovalRecordLock,
		FinalRejectionRecordLock: value.FinalRejectionRecordLock, EntryCriteria: criteriaFromDocument(value.EntryCriteria),
		ProcessOrder: value.ProcessOrder, HierarchyRouting: hierarchyRoutingFromDocument(value.HierarchyRouting),
		Steps: stepListFromDocument(value.Steps), InitialSubmissionActions: actionListFromDocument(value.InitialSubmissionActions),
		StepApprovalActions: actionListFromDocument(value.StepApprovalActions), FinalApprovalActions: actionListFromDocument(value.FinalApprovalActions),
		FinalRejectionActions: actionListFromDocument(value.FinalRejectionActions), RecallActions: actionListFromDocument(value.RecallActions),
		ReassignActions: actionListFromDocument(value.ReassignActions),
	}
}

func approvalPolicyToDocument(value ApprovalPolicy) approvalPolicyDocument {
	result := approvalPolicyDocument{RecordEditability: value.RecordEditability, AllowedSubmitters: make([]allowedSubmitterDocument, len(value.AllowedSubmitters))}
	for index, submitter := range value.AllowedSubmitters {
		result.AllowedSubmitters[index] = allowedSubmitterDocument{Submitter: submitter.Submitter, Kind: submitter.Kind}
	}
	return result
}

func approvalPolicyFromDocument(value approvalPolicyDocument) ApprovalPolicy {
	result := ApprovalPolicy{RecordEditability: value.RecordEditability, AllowedSubmitters: make([]AllowedSubmitter, len(value.AllowedSubmitters))}
	for index, submitter := range value.AllowedSubmitters {
		result.AllowedSubmitters[index] = AllowedSubmitter{Submitter: submitter.Submitter, Kind: submitter.Kind}
	}
	return result
}

func hierarchyRoutingToDocument(value *HierarchyRouting) *hierarchyRoutingDocument {
	if value == nil {
		return nil
	}
	return &hierarchyRoutingDocument{UserHierarchyField: value.UserHierarchyField, InitialPrincipal: value.InitialPrincipal}
}

func hierarchyRoutingFromDocument(value *hierarchyRoutingDocument) *HierarchyRouting {
	if value == nil {
		return nil
	}
	return &HierarchyRouting{UserHierarchyField: value.UserHierarchyField, InitialPrincipal: value.InitialPrincipal}
}

func stepListToDocument(values []Step) []stepDocument {
	result := make([]stepDocument, len(values))
	for index, value := range values {
		result[index] = stepDocument{
			Name: value.Name, Label: value.Label, Description: value.Description, AllowDelegate: value.AllowDelegate,
			AllowComments: value.AllowComments, RequireComments: value.RequireComments, Approvers: approverListToDocument(value.Approvers),
			MultipleApproverMode: value.MultipleApproverMode, RejectBehavior: value.RejectBehavior, Criteria: criteriaToDocument(value.Criteria),
			IfCriteriaNotMet: value.IfCriteriaNotMet, ApprovalActions: actionListToDocument(value.ApprovalActions),
			RejectionActions: actionListToDocument(value.RejectionActions),
		}
	}
	return result
}

func stepListFromDocument(values []stepDocument) []Step {
	result := make([]Step, len(values))
	for index, value := range values {
		result[index] = Step{
			Name: value.Name, Label: value.Label, Description: value.Description, AllowDelegate: value.AllowDelegate,
			AllowComments: value.AllowComments, RequireComments: value.RequireComments, Approvers: approverListFromDocument(value.Approvers),
			MultipleApproverMode: value.MultipleApproverMode, RejectBehavior: value.RejectBehavior, Criteria: criteriaFromDocument(value.Criteria),
			IfCriteriaNotMet: value.IfCriteriaNotMet, ApprovalActions: actionListFromDocument(value.ApprovalActions),
			RejectionActions: actionListFromDocument(value.RejectionActions),
		}
	}
	return result
}

func approverListToDocument(values []Approver) []approverDocument {
	result := make([]approverDocument, len(values))
	for index, value := range values {
		result[index] = approverDocument{Kind: value.Kind, Reference: value.Reference}
	}
	return result
}

func approverListFromDocument(values []approverDocument) []Approver {
	result := make([]Approver, len(values))
	for index, value := range values {
		result[index] = Approver{Kind: value.Kind, Reference: value.Reference}
	}
	return result
}

func criteriaToDocument(value *Criteria) *criteriaDocument {
	if value == nil {
		return nil
	}
	result := &criteriaDocument{Mode: value.Mode, Formula: value.Formula, BooleanFilter: value.BooleanFilter, Items: make([]criteriaItemDocument, len(value.Items))}
	for index, item := range value.Items {
		result.Items[index] = criteriaItemDocument{Field: item.Field, Operation: item.Operation, Value: item.Value}
	}
	return result
}

func criteriaFromDocument(value *criteriaDocument) *Criteria {
	if value == nil {
		return nil
	}
	result := &Criteria{Mode: value.Mode, Formula: value.Formula, BooleanFilter: value.BooleanFilter, Items: make([]CriteriaItem, len(value.Items))}
	for index, item := range value.Items {
		result.Items[index] = CriteriaItem{Field: item.Field, Operation: item.Operation, Value: item.Value}
	}
	return result
}

func actionListToDocument(values []Action) []actionDocument {
	result := make([]actionDocument, len(values))
	for index, value := range values {
		result[index] = actionDocument{Name: value.Name, Kind: value.Kind}
		if value.Reference != nil {
			result[index].Reference = &actionReferenceDocument{APIName: value.Reference.APIName}
		}
		if value.FieldUpdate != nil {
			result[index].FieldUpdate = &fieldUpdateActionDocument{FieldAPIName: value.FieldUpdate.FieldAPIName, Value: fieldUpdateValueToDocument(value.FieldUpdate.Value)}
		}
		if value.Flow != nil {
			result[index].Flow = &flowActionDocument{APIName: value.Flow.APIName}
		}
		if value.Alert != nil {
			result[index].Alert = &alertActionDocument{EmailTemplateID: value.Alert.EmailTemplateID}
		}
	}
	return result
}

func actionListFromDocument(values []actionDocument) []Action {
	result := make([]Action, len(values))
	for index, value := range values {
		result[index] = Action{Name: value.Name, Kind: value.Kind}
		if value.Reference != nil {
			result[index].Reference = &ActionReference{APIName: value.Reference.APIName}
		}
		if value.FieldUpdate != nil {
			result[index].FieldUpdate = &FieldUpdateAction{FieldAPIName: value.FieldUpdate.FieldAPIName, Value: fieldUpdateValueFromDocument(value.FieldUpdate.Value)}
		}
		if value.Flow != nil {
			result[index].Flow = &FlowAction{APIName: value.Flow.APIName}
		}
		if value.Alert != nil {
			result[index].Alert = &AlertAction{EmailTemplateID: value.Alert.EmailTemplateID}
		}
	}
	return result
}

func fieldUpdateValueToDocument(value FieldUpdateValue) fieldUpdateValueDocument {
	result := fieldUpdateValueDocument{Kind: value.Kind, Null: value.Null, SourceText: value.SourceText, String: value.String, Boolean: value.Boolean, Integer: value.Integer, Date: value.Date, DateTime: value.DateTime, Reference: value.Reference}
	if value.Decimal != nil {
		result.Decimal = &decimalDocument{Coefficient: value.Decimal.Coefficient, Scale: value.Decimal.Scale}
	}
	return result
}

func fieldUpdateValueFromDocument(value fieldUpdateValueDocument) FieldUpdateValue {
	result := FieldUpdateValue{Kind: value.Kind, Null: value.Null, SourceText: value.SourceText, String: value.String, Boolean: value.Boolean, Integer: value.Integer, Date: value.Date, DateTime: value.DateTime, Reference: value.Reference}
	if value.Decimal != nil {
		result.Decimal = &Decimal{Coefficient: value.Decimal.Coefficient, Scale: value.Decimal.Scale}
	}
	return result
}
