package issuetrackerconformance

import (
	"bytes"
	"encoding/base64"
	"encoding/binary"
	"encoding/json"
	"fmt"
	"io"
	"regexp"
	"sort"
	"strings"
	"time"

	cp "github.com/pingcap-inc/nexuscrm-contract/pkg/controlplane"
)

const (
	IssueWorkflowComponentKind cp.ComponentKind = "conformance_issue_workflow"

	issueWorkflowTable           = "ConformanceIssueWorkflow"
	issueWorkflowStatusTable     = "ConformanceIssueWorkflowStatus"
	issueWorkflowTransitionTable = "ConformanceIssueWorkflowTransition"
	issueWorkflowOwnerGuardTable = "ConformanceIssueWorkflowOwnerGuard"

	issueWorkflowTargetDomain     = "conformance/issue_workflow/project_workflow/binary-v1"
	issueWorkflowSetTargetDomain  = "conformance/issue_workflow/exact_set/global-v1"
	issueWorkflowMaximumRows      = 8
	issueWorkflowMaximumChildRows = issueWorkflowMaximumRows * issueWorkflowMaximumRows
	issueWorkflowMaximumText      = 255
)

const IssueWorkflowRevisionEffectClass cp.ComponentManagedEffectClass = "conformance_issue_workflow_revision"

var issueWorkflowKeyPattern = regexp.MustCompile(`^[A-Za-z][A-Za-z0-9_]{0,63}$`)

type IssueWorkflowStatus struct {
	Key   string `json:"key"`
	Label string `json:"label"`
}

type IssueWorkflowTransition struct {
	Key  string `json:"key"`
	From string `json:"from"`
	To   string `json:"to"`
}

type IssueWorkflowDeclaration struct {
	ProjectKey  string                    `json:"project_key"`
	WorkflowKey string                    `json:"workflow_key"`
	Name        string                    `json:"name"`
	Statuses    []IssueWorkflowStatus     `json:"statuses"`
	Transitions []IssueWorkflowTransition `json:"transitions"`
}

type issueWorkflowDesired struct {
	Projection  IssueWorkflowDeclaration
	ProjectKey  []byte
	WorkflowKey []byte
}

// NewIssueWorkflowManagedLifecycleRegistration exposes a CI-only Adapter over
// the public lifecycle contract. It creates no product route, pack, service,
// release, or deployment surface.
func NewIssueWorkflowManagedLifecycleRegistration() (cp.ComponentKindRegistration, cp.ComponentManagedLifecycleRegistration, error) {
	base := cp.ComponentKindRegistration{
		Contract: cp.ComponentKindContract{
			Kind:  IssueWorkflowComponentKind,
			Phase: cp.InstallPlanPhaseAutomation,
			WriteSet: []cp.ComponentWriteSurface{
				{Table: issueWorkflowTable, Fact: "issue workflow aggregate"},
				{Table: issueWorkflowStatusTable, Fact: "issue workflow keyed statuses"},
				{Table: issueWorkflowTransitionTable, Fact: "issue workflow keyed transitions"},
			},
			BusinessKey:        "base64url project key / base64url workflow key",
			OwnerDiscriminator: cp.ComponentOwnerStrategyOwnerPack,
			Manageabilities:    issueWorkflowManageabilities(),
			VerifyProjection:   "owner-scoped workflow, unordered keyed statuses, and unordered keyed transitions",
			CollisionKey:       "binary project key / binary workflow key",
		},
		Capability: cp.SourceBackedComponentCapability{
			Kind: IssueWorkflowComponentKind, Installable: true, Manageabilities: issueWorkflowManageabilities(),
		},
		AllowedCategories: []cp.PackCategory{cp.AppPack},
		BuildHandler:      buildIssueWorkflowManagedComponent,
	}
	lifecycle, err := cp.NewComponentManagedLifecycleRegistration(cp.ComponentManagedLifecycleRegistrationSpec{
		Kind: IssueWorkflowComponentKind,
		ReadSet: []cp.ComponentReadSurface{
			{Table: issueWorkflowOwnerGuardTable, Fact: "read-only owner enablement guard"},
			{Table: issueWorkflowTable, Fact: "issue workflow aggregate and exact owner set"},
			{Table: issueWorkflowStatusTable, Fact: "issue workflow keyed statuses"},
			{Table: issueWorkflowTransitionTable, Fact: "issue workflow keyed transitions"},
		},
		Capabilities: []cp.ComponentManagedCapability{
			cp.ComponentManagedCapabilityExactSet,
			cp.ComponentManagedCapabilityTargetSerialization,
			cp.ComponentManagedCapabilityStructuredMutation,
			cp.ComponentManagedCapabilityEffects,
			cp.ComponentManagedCapabilityEvidence,
			cp.ComponentManagedCapabilityFreshProjection,
		},
		EffectClasses:   []cp.ComponentManagedEffectClass{IssueWorkflowRevisionEffectClass},
		TransactionMode: cp.ComponentManagedTransactionPessimisticReadCommitted,
		Budget: cp.ComponentManagedBudget{
			MaxTargets:            17,
			MaxCurrentItems:       273,
			MaxDesiredItems:       17,
			MaxDeltaRows:          136,
			MaxMutationBytes:      1 << 20,
			MaxMutationStatements: 24,
			MaxBatchRows:          issueWorkflowMaximumChildRows,
			MaxEvidenceWrites:     1,
			MaxEffectCalls:        1,
			MaxLockWait:           5 * time.Second,
			MaxElapsed:            30 * time.Second,
			MaxAttempts:           3,
		},
		Lifecycle: cp.ComponentManagedLifecycleProvider{
			Plan: issueWorkflowLifecyclePlan, Mutate: issueWorkflowLifecycleMutate, Project: issueWorkflowLifecycleProject,
		},
	})
	if err != nil {
		return cp.ComponentKindRegistration{}, cp.ComponentManagedLifecycleRegistration{}, fmt.Errorf("issue workflow conformance lifecycle: %w", err)
	}
	return base, lifecycle, nil
}

func issueWorkflowManageabilities() map[cp.Manageability]struct{} {
	return map[cp.Manageability]struct{}{cp.ManageabilityLocked: {}}
}

// NewIssueWorkflowManagedRequest builds the immutable public request used by
// the fixture's lifecycle and by later host conformance composition.
func NewIssueWorkflowManagedRequest(packName string, payload []byte) (cp.ComponentManagedRequest, error) {
	if packName == "" || strings.TrimSpace(packName) != packName || len(packName) > issueWorkflowMaximumText {
		return cp.ComponentManagedRequest{}, fmt.Errorf("issue workflow pack name %q is not canonical", packName)
	}
	desired, err := decodeIssueWorkflowDeclaration(payload)
	if err != nil {
		return cp.ComponentManagedRequest{}, err
	}
	expected, err := json.Marshal(desired.Projection)
	if err != nil {
		return cp.ComponentManagedRequest{}, fmt.Errorf("marshal issue workflow projection: %w", err)
	}
	businessKey := issueWorkflowBusinessKey(desired.ProjectKey, desired.WorkflowKey)
	return cp.ComponentManagedRequest{
		PackName: packName,
		Component: cp.ManifestComponent{
			Kind: IssueWorkflowComponentKind, BusinessKey: businessKey, Manageability: cp.ManageabilityLocked,
			Payload: append([]byte(nil), payload...),
		},
		Payload: append([]byte(nil), payload...), ExpectedProjection: expected,
	}, nil
}

func buildIssueWorkflowManagedComponent(buildContext cp.ComponentBuildContext, request cp.ComponentBuildRequest) error {
	if buildContext == nil {
		return fmt.Errorf("issue workflow build context is required")
	}
	managed, err := NewIssueWorkflowManagedRequest(request.PackName, request.Payload)
	if err != nil {
		return err
	}
	if request.PackName != buildContext.PackName() {
		return fmt.Errorf("issue workflow build pack name %q does not match context pack %q", request.PackName, buildContext.PackName())
	}
	if request.Component.Kind != managed.Component.Kind || request.Component.BusinessKey != managed.Component.BusinessKey || request.Component.Manageability != managed.Component.Manageability {
		return fmt.Errorf("issue workflow build component identity does not match its canonical payload")
	}
	if len(request.Component.Payload) > 0 && !bytes.Equal(request.Component.Payload, request.Payload) {
		return fmt.Errorf("issue workflow component payload must exactly match the build payload")
	}
	projectKey, workflowKey, err := parseIssueWorkflowBusinessKey(managed.Component.BusinessKey)
	if err != nil {
		return err
	}
	descriptor := cp.ComponentBuildManagedComponent{
		ExpectedProjection: managed.ExpectedProjection,
		CollisionClaims: []cp.ComponentCollisionClaim{{
			Namespace: issueWorkflowTargetDomain,
			Key:       base64.RawURLEncoding.EncodeToString(issueWorkflowCompoundTargetKey(projectKey, workflowKey)),
		}},
	}
	if err := descriptor.Validate(); err != nil {
		return fmt.Errorf("issue workflow managed descriptor: %w", err)
	}
	if err := buildContext.AddManagedComponent(descriptor); err != nil {
		return fmt.Errorf("add issue workflow managed component: %w", err)
	}
	return nil
}

func decodeIssueWorkflowManagedRequest(request cp.ComponentManagedRequest) (issueWorkflowDesired, error) {
	if request.PackName == "" || strings.TrimSpace(request.PackName) != request.PackName || len(request.PackName) > issueWorkflowMaximumText {
		return issueWorkflowDesired{}, fmt.Errorf("issue workflow pack name %q is not canonical", request.PackName)
	}
	if request.SourceOwner != "" {
		return issueWorkflowDesired{}, fmt.Errorf("pack-owned issue workflow must not declare source owner %q", request.SourceOwner)
	}
	if request.Component.Kind != IssueWorkflowComponentKind {
		return issueWorkflowDesired{}, fmt.Errorf("issue workflow component kind = %q, want %q", request.Component.Kind, IssueWorkflowComponentKind)
	}
	if request.Component.Manageability != cp.ManageabilityLocked {
		return issueWorkflowDesired{}, fmt.Errorf("issue workflow manageability must be %q", cp.ManageabilityLocked)
	}
	if len(request.Payload) == 0 || (len(request.Component.Payload) > 0 && !bytes.Equal(request.Component.Payload, request.Payload)) {
		return issueWorkflowDesired{}, fmt.Errorf("issue workflow lifecycle payload is required and any inline payload must exactly match it")
	}
	desired, err := decodeIssueWorkflowDeclaration(request.Payload)
	if err != nil {
		return issueWorkflowDesired{}, err
	}
	if request.Component.BusinessKey != issueWorkflowBusinessKey(desired.ProjectKey, desired.WorkflowKey) {
		return issueWorkflowDesired{}, fmt.Errorf("issue workflow business key %q does not match its canonical payload", request.Component.BusinessKey)
	}
	expected, err := json.Marshal(desired.Projection)
	if err != nil {
		return issueWorkflowDesired{}, err
	}
	if !bytes.Equal(expected, request.ExpectedProjection) {
		return issueWorkflowDesired{}, fmt.Errorf("issue workflow expected projection does not match its canonical payload")
	}
	return desired, nil
}

func decodeIssueWorkflowDeclaration(payload []byte) (issueWorkflowDesired, error) {
	if len(payload) == 0 {
		return issueWorkflowDesired{}, fmt.Errorf("issue workflow payload is required")
	}
	var declaration IssueWorkflowDeclaration
	decoder := json.NewDecoder(bytes.NewReader(payload))
	decoder.DisallowUnknownFields()
	if err := decoder.Decode(&declaration); err != nil {
		return issueWorkflowDesired{}, fmt.Errorf("decode issue workflow declaration: %w", err)
	}
	if err := rejectIssueWorkflowTrailingJSON(decoder); err != nil {
		return issueWorkflowDesired{}, err
	}
	projectKey, err := decodeIssueWorkflowBinaryKey("project_key", declaration.ProjectKey)
	if err != nil {
		return issueWorkflowDesired{}, err
	}
	workflowKey, err := decodeIssueWorkflowBinaryKey("workflow_key", declaration.WorkflowKey)
	if err != nil {
		return issueWorkflowDesired{}, err
	}
	declaration.ProjectKey = base64.RawURLEncoding.EncodeToString(projectKey)
	declaration.WorkflowKey = base64.RawURLEncoding.EncodeToString(workflowKey)
	if declaration.Name == "" || strings.TrimSpace(declaration.Name) != declaration.Name || len(declaration.Name) > issueWorkflowMaximumText {
		return issueWorkflowDesired{}, fmt.Errorf("issue workflow name is required and must be canonical")
	}
	statuses := make(map[string]IssueWorkflowStatus, len(declaration.Statuses))
	for index, status := range declaration.Statuses {
		if !issueWorkflowKeyPattern.MatchString(status.Key) {
			return issueWorkflowDesired{}, fmt.Errorf("issue workflow status %d key %q is not canonical", index, status.Key)
		}
		if status.Label == "" || strings.TrimSpace(status.Label) != status.Label || len(status.Label) > issueWorkflowMaximumText {
			return issueWorkflowDesired{}, fmt.Errorf("issue workflow status %q label is required and must be canonical", status.Key)
		}
		if _, duplicate := statuses[status.Key]; duplicate {
			return issueWorkflowDesired{}, fmt.Errorf("issue workflow status key %q is duplicated", status.Key)
		}
		statuses[status.Key] = status
	}
	if len(statuses) == 0 {
		return issueWorkflowDesired{}, fmt.Errorf("issue workflow requires at least one status")
	}
	transitions := make(map[string]IssueWorkflowTransition, len(declaration.Transitions))
	for index, transition := range declaration.Transitions {
		if !issueWorkflowKeyPattern.MatchString(transition.Key) {
			return issueWorkflowDesired{}, fmt.Errorf("issue workflow transition %d key %q is not canonical", index, transition.Key)
		}
		if _, ok := statuses[transition.From]; !ok {
			return issueWorkflowDesired{}, fmt.Errorf("issue workflow transition %q references missing from status %q", transition.Key, transition.From)
		}
		if _, ok := statuses[transition.To]; !ok {
			return issueWorkflowDesired{}, fmt.Errorf("issue workflow transition %q references missing to status %q", transition.Key, transition.To)
		}
		if _, duplicate := transitions[transition.Key]; duplicate {
			return issueWorkflowDesired{}, fmt.Errorf("issue workflow transition key %q is duplicated", transition.Key)
		}
		transitions[transition.Key] = transition
	}
	declaration.Statuses = make([]IssueWorkflowStatus, 0, len(statuses))
	for _, status := range statuses {
		declaration.Statuses = append(declaration.Statuses, status)
	}
	sort.Slice(declaration.Statuses, func(i, j int) bool { return declaration.Statuses[i].Key < declaration.Statuses[j].Key })
	declaration.Transitions = make([]IssueWorkflowTransition, 0, len(transitions))
	for _, transition := range transitions {
		declaration.Transitions = append(declaration.Transitions, transition)
	}
	sort.Slice(declaration.Transitions, func(i, j int) bool { return declaration.Transitions[i].Key < declaration.Transitions[j].Key })
	return issueWorkflowDesired{Projection: declaration, ProjectKey: projectKey, WorkflowKey: workflowKey}, nil
}

func rejectIssueWorkflowTrailingJSON(decoder *json.Decoder) error {
	var extra any
	if err := decoder.Decode(&extra); err != io.EOF {
		if err == nil {
			return fmt.Errorf("issue workflow payload must contain exactly one JSON value")
		}
		return fmt.Errorf("decode issue workflow trailing payload: %w", err)
	}
	return nil
}

func decodeIssueWorkflowBinaryKey(field, value string) ([]byte, error) {
	decoded, err := base64.RawURLEncoding.DecodeString(value)
	if err != nil || len(decoded) == 0 || len(decoded) > 255 || base64.RawURLEncoding.EncodeToString(decoded) != value {
		return nil, fmt.Errorf("issue workflow %s must be canonical unpadded base64url encoding of 1..255 bytes", field)
	}
	return decoded, nil
}

func issueWorkflowBusinessKey(projectKey, workflowKey []byte) string {
	return base64.RawURLEncoding.EncodeToString(projectKey) + "/" + base64.RawURLEncoding.EncodeToString(workflowKey)
}

func parseIssueWorkflowBusinessKey(businessKey string) ([]byte, []byte, error) {
	projectText, workflowText, found := strings.Cut(businessKey, "/")
	if !found || strings.Contains(workflowText, "/") {
		return nil, nil, fmt.Errorf("issue workflow business key %q must be canonical <project_key>/<workflow_key>", businessKey)
	}
	projectKey, err := decodeIssueWorkflowBinaryKey("business project key", projectText)
	if err != nil {
		return nil, nil, err
	}
	workflowKey, err := decodeIssueWorkflowBinaryKey("business workflow key", workflowText)
	if err != nil {
		return nil, nil, err
	}
	return projectKey, workflowKey, nil
}

func issueWorkflowCompoundTargetKey(projectKey, workflowKey []byte) []byte {
	value := make([]byte, 1+4+len(projectKey)+4+len(workflowKey))
	value[0] = 1
	binary.BigEndian.PutUint32(value[1:5], uint32(len(projectKey)))
	copy(value[5:], projectKey)
	offset := 5 + len(projectKey)
	binary.BigEndian.PutUint32(value[offset:offset+4], uint32(len(workflowKey)))
	copy(value[offset+4:], workflowKey)
	return value
}

func issueWorkflowItemTarget(projectKey, workflowKey []byte) cp.ComponentManagedTarget {
	return cp.ComponentManagedTarget{TargetDomain: issueWorkflowTargetDomain, PhysicalKeyBytes: issueWorkflowCompoundTargetKey(projectKey, workflowKey)}
}

func issueWorkflowSetTarget() cp.ComponentManagedTarget {
	return cp.ComponentManagedTarget{TargetDomain: issueWorkflowSetTargetDomain, PhysicalKeyBytes: []byte{0}}
}

func issueWorkflowItemProjectionIdentity(request cp.ComponentManagedRequest) []byte {
	return []byte(string(IssueWorkflowComponentKind) + "\x00" + request.PackName + "\x00" + request.Component.BusinessKey)
}

func issueWorkflowSetProjectionIdentity(request cp.ComponentManagedSetRequest) []byte {
	return []byte(string(IssueWorkflowComponentKind) + "\x00" + request.PackName + "\x00*")
}
