package model

import (
	"bytes"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io"
	"reflect"
	"strconv"
	"strings"
	"time"
)

// CredentialStorageScheme is the storage scheme identifier for encrypted app credentials.
const CredentialStorageScheme = "aes-gcm-v1"

// JSONDocument is one opaque, syntactically valid JSON value stored in a DB
// JSON column. Domain stores keep the document typed without owning JSON
// codecs; validation and codecs remain centralized in this file.
type JSONDocument = json.RawMessage

// CallbackRecoveryActionReceipt is the idempotency marker embedded in a
// workflow effect result for the permanent non-terminal pause action. Park is
// a repeatable recovery FSM transition and deliberately never writes this
// lifecycle-wide receipt. It is kept with the store JSON codecs so the
// workflow-run store does not own a second JSON contract implementation.
type CallbackRecoveryActionReceipt struct {
	CallbackAction               string `json:"callbackAction"`
	CallbackActionIdempotencyKey string `json:"callbackActionIdempotencyKey"`
}

func DecodeCallbackRecoveryActionReceipt(raw []byte) (string, string, error) {
	if len(bytes.TrimSpace(raw)) == 0 {
		return "", "", nil
	}
	var value CallbackRecoveryActionReceipt
	if err := json.Unmarshal(raw, &value); err != nil {
		return "", "", err
	}
	return strings.TrimSpace(value.CallbackAction), strings.TrimSpace(value.CallbackActionIdempotencyKey), nil
}

func WriteCallbackRecoveryActionReceipt(raw []byte, action, idempotencyKey string) ([]byte, error) {
	fields, err := decodeCallbackRecoveryReceiptFields(raw)
	if err != nil {
		return nil, err
	}
	encodedAction, _ := json.Marshal(action)
	encodedKey, _ := json.Marshal(idempotencyKey)
	fields["callbackAction"] = encodedAction
	fields["callbackActionIdempotencyKey"] = encodedKey
	encoded, err := json.Marshal(fields)
	if err != nil {
		return nil, fmt.Errorf("encode workflow Run callback recovery receipt: %w", err)
	}
	return encoded, nil
}

// ClearCallbackRecoveryActionReceipt removes the legacy park receipt fields.
// New park cycles never write them; clearing them when an old row is touched
// prevents the compatibility shape from remaining a lifecycle-wide decision.
func ClearCallbackRecoveryActionReceipt(raw []byte) ([]byte, error) {
	fields, err := decodeCallbackRecoveryReceiptFields(raw)
	if err != nil {
		return nil, err
	}
	delete(fields, "callbackAction")
	delete(fields, "callbackActionIdempotencyKey")
	encoded, err := json.Marshal(fields)
	if err != nil {
		return nil, fmt.Errorf("clear workflow Run callback recovery receipt: %w", err)
	}
	return encoded, nil
}

func decodeCallbackRecoveryReceiptFields(raw []byte) (map[string]json.RawMessage, error) {
	var fields map[string]json.RawMessage
	if len(bytes.TrimSpace(raw)) == 0 {
		return make(map[string]json.RawMessage), nil
	}
	if err := json.Unmarshal(raw, &fields); err != nil || fields == nil {
		return nil, fmt.Errorf("workflow Run callback recovery receipt is invalid")
	}
	return fields, nil
}

// CredentialEnvelope is the storage envelope for encrypted app secrets.
type CredentialEnvelope struct {
	Storage    string `json:"_storage"`
	Ciphertext string `json:"ciphertext"`
}

// WorkspaceMemberDirectoryCursor is the cursor type for paginated workspace member directory listings.
type WorkspaceMemberDirectoryCursor struct {
	JoinedAt time.Time `json:"joinedAt"`
	MemberID string    `json:"memberId"`
}

// MachineListCursor is the cursor type for paginated workspace machine listings.
type MachineListCursor struct {
	CreatedAt time.Time `json:"createdAt"`
	ID        string    `json:"id"`
}

// KnowledgeQAAuthorizedRootJSON is the persisted evidence-root snapshot stored
// on one server-hosted Knowledge Q&A turn.
type KnowledgeQAAuthorizedRootJSON struct {
	ID              string `json:"id"`
	KnowledgeBaseID string `json:"knowledgeBaseId"`
	SourceID        string `json:"sourceId,omitempty"`
	RootHandleRef   string `json:"rootHandleRef"`
	RootKind        string `json:"rootKind"`
	RepoRole        string `json:"repoRole"`
	Revision        string `json:"revision"`
	FreshnessStatus string `json:"freshnessStatus,omitempty"`
}

// ResourceMemoryCommitDetailsJSON is the persisted resource-activity detail
// subset used to associate a managed Memory commit with its branch.
type ResourceMemoryCommitDetailsJSON struct {
	Ref string `json:"ref"`
}

// AppRuntimeCapabilityConfigJSON is the stored App capability config shape
// used to select the approved external runtime transports.
type AppRuntimeCapabilityConfigJSON struct {
	Runtime AppRuntimeCapabilityRuntimeJSON `json:"runtime"`
}

// AppRuntimeCapabilityRuntimeJSON is the normalized runtime transport list.
type AppRuntimeCapabilityRuntimeJSON struct {
	Transports []string `json:"transports"`
}

// ExtensionDefinitionManifestJSON is the durable, secret-free manifest stored
// in service_definitions.manifest for one immutable Extension Definition.
type ExtensionDefinitionManifestJSON struct {
	Domain          string `json:"domain"`
	Version         int    `json:"version"`
	Digest          string `json:"digest"`
	EndpointBaseURL string `json:"endpointBaseUrl,omitempty"`
}

// ExtensionMCPToolsConfigJSON is the legacy App capability shape projected
// into the closed Built-in Extension action set.
type ExtensionMCPToolsConfigJSON struct {
	Tools []string `json:"tools"`
}

// WatchEventSubscriptionFilter identifies a pull-request subscription event source.
type WatchEventSubscriptionFilter struct {
	ResourceIdentifier string `json:"resourceIdentifier"`
	ConnectionRef      string `json:"connectionRef"`
}

// WatchSourceConfig holds legacy watch compatibility source configuration.
type WatchSourceConfig struct {
	Provider              string                       `json:"provider"`
	EventFamily           string                       `json:"event_family"`
	ResourceKind          string                       `json:"resource_kind"`
	ResourceRef           string                       `json:"resource_ref"`
	ResourceKey           string                       `json:"resource_key"`
	ProviderConnectionRef string                       `json:"provider_connection_ref,omitempty"`
	EventFilters          map[string]any               `json:"event_filters,omitempty"`
	EventKinds            []string                     `json:"eventKinds,omitempty"`
	Filter                WatchEventSubscriptionFilter `json:"filter"`
}

// WatchOwnerSnapshot captures the app owner identity at watch creation time.
type WatchOwnerSnapshot struct {
	AppID             string `json:"app_id"`
	AppInstallationID string `json:"app_installation_id"`
	AppMemberID       string `json:"app_member_id"`
}

// WatchEventFilters holds canonical event names for pull-request subscription filters.
type WatchEventFilters struct {
	Events []string `json:"events"`
}

// WatchRuntimeOwnerSnapshot captures runtime principal identity for provider watches.
type WatchRuntimeOwnerSnapshot struct {
	PrincipalKind     string `json:"principal_kind"`
	AppID             string `json:"app_id"`
	AppInstallationID string `json:"app_installation_id"`
	AppMemberID       string `json:"app_member_id"`
	AppBindingID      string `json:"app_binding_id"`
}

// ProviderEventPayload holds the payload captured for a service event delivery.
type ProviderEventPayload struct {
	Provider           string            `json:"provider"`
	ResourceIdentifier string            `json:"resource_identifier"`
	ResourceURL        string            `json:"resource_url"`
	EventID            string            `json:"event_id"`
	DeliveryKey        string            `json:"delivery_key"`
	EventType          string            `json:"event_type"`
	EventKind          string            `json:"event_kind"`
	ActionKind         string            `json:"action_kind"`
	ActorLogin         string            `json:"actor_login"`
	BodyExcerpt        string            `json:"body_excerpt"`
	ChangedPaths       []string          `json:"changed_paths,omitempty"`
	Extra              map[string]string `json:"extra,omitempty"`
}

// ProviderTokenConfig holds the configuration payload for an organization provider token.
type ProviderTokenConfig struct {
	Role                string   `json:"role,omitempty"`
	BaseURL             string   `json:"baseUrl,omitempty"`
	Mode                string   `json:"mode,omitempty"`
	BackendName         string   `json:"backendName,omitempty"`
	BackendID           int      `json:"backendId,omitempty"`
	Active              *bool    `json:"active,omitempty"`
	Weight              float64  `json:"weight,omitempty"`
	WeightDiffThreshold float64  `json:"weightDiffThreshold,omitempty"`
	AdaptiveWeights     bool     `json:"adaptiveWeights,omitempty"`
	RequiresOAuthBeta   bool     `json:"requiresOAuthBeta,omitempty"`
	AllowedAPIKeys      []string `json:"allowedApiKeys,omitempty"`
	CustomPrompt        string   `json:"customPrompt,omitempty"`
	CustomPromptModels  []string `json:"customPromptModels,omitempty"`
}

// ProviderTokenMaterial holds the secret material for an organization provider token.
type ProviderTokenMaterial struct {
	Token           string `json:"token,omitempty"`
	APIKey          string `json:"apiKey,omitempty"`
	AuthToken       string `json:"authToken,omitempty"`
	AuthJSONContent string `json:"authJsonContent,omitempty"`
}

// SignedProviderProxyKey is the payload embedded in a signed organization LLM proxy key.
type SignedProviderProxyKey struct {
	WorkspaceID   string `json:"workspaceId"`
	ExpiresAtUnix int64  `json:"expiresAtUnix"`
}

// ProviderEndpoints holds the endpoint URLs for an organization provider.
type ProviderEndpoints struct {
	MessagesBaseURL   string `json:"messagesBaseUrl,omitempty"`
	ResponsesBaseURL  string `json:"responsesBaseUrl,omitempty"`
	EmbeddingsBaseURL string `json:"embeddingsBaseUrl,omitempty"`
}

// ProviderCapabilities describes the compatibility capabilities of an organization provider.
type ProviderCapabilities struct {
	OpenAICompatible    bool `json:"openaiCompatible"`
	AnthropicCompatible bool `json:"anthropicCompatible"`
	ResponsesCompatible bool `json:"responsesCompatible"`
	EmbeddingCompatible bool `json:"embeddingCompatible"`
}

// ProviderModel represents a model offered by an organization provider.
type ProviderModel struct {
	Name string `json:"name"`
	ID   string `json:"id"`
}

// GitHubInstallationTokenResponse is the response from the GitHub App installation access token API.
type GitHubInstallationTokenResponse struct {
	Token string `json:"token"`
}

// GitHubCommitPullRequest represents a pull request returned by the GitHub commit PRs API.
type GitHubCommitPullRequest struct {
	Number  json.Number `json:"number"`
	HTMLURL string      `json:"html_url"`
}

// GitHubAppJWTHeader is the JWT header for GitHub App authentication.
type GitHubAppJWTHeader struct {
	Algorithm string `json:"alg"`
	Type      string `json:"typ"`
}

// GitHubAppJWTClaims is the JWT claims set for GitHub App authentication.
type GitHubAppJWTClaims struct {
	IssuedAt  int64  `json:"iat"`
	ExpiresAt int64  `json:"exp"`
	Issuer    string `json:"iss"`
}

// TaskBoardViewStateJSON is the persisted JSON object for one member's task-board view preferences.
type TaskBoardViewStateJSON struct {
	View              string              `json:"view"`
	SearchQuery       string              `json:"searchQuery"`
	QuickFilters      []string            `json:"quickFilters"`
	StructuredFilters map[string][]string `json:"structuredFilters"`
	SortMode          string              `json:"sortMode"`
	// CardPropertyToggles is retained for old-client and persisted-row
	// compatibility. New clients keep display preferences locally.
	CardPropertyToggles []string `json:"cardPropertyToggles"`
	DoneCollapsed       bool     `json:"doneCollapsed"`
}

func MarshalTaskBoardViewState(value TaskBoardViewStateJSON) ([]byte, error) {
	return json.Marshal(value)
}

func UnmarshalTaskBoardViewState(raw []byte) (TaskBoardViewStateJSON, error) {
	var value TaskBoardViewStateJSON
	if err := json.Unmarshal(raw, &value); err != nil {
		return TaskBoardViewStateJSON{}, err
	}
	return value, nil
}

// TaskCollaboratorSourceJSON is the stored audit JSON for why a member could write task discussion.
type TaskCollaboratorSourceJSON struct {
	ScopeType  string `json:"scopeType"`
	ScopeID    string `json:"scopeId"`
	RelationID string `json:"relationId"`
	Role       string `json:"role"`
}

func MarshalTaskCollaboratorSources(value []TaskCollaboratorSourceJSON) ([]byte, error) {
	return json.Marshal(value)
}

// MarshalTaskStringList stores task string-list JSON columns such as review
// questions and context refs.
func MarshalTaskStringList(values []string) ([]byte, error) {
	return json.Marshal(values)
}

func UnmarshalTaskStringList(raw []byte) ([]string, error) {
	if len(raw) == 0 {
		return []string{}, nil
	}
	var values []string
	if err := json.Unmarshal(raw, &values); err != nil {
		return nil, err
	}
	return values, nil
}

// MarshalJSONMap stores generic JSON object columns used by store projections.
func MarshalJSONMap(value map[string]any, defaultEmpty bool) ([]byte, error) {
	if len(value) == 0 {
		if defaultEmpty {
			return []byte("{}"), nil
		}
		return nil, nil
	}
	return json.Marshal(value)
}

// UnmarshalJSONMap reads generic JSON object columns used by store projections.
func UnmarshalJSONMap(raw []byte) (map[string]any, error) {
	if len(raw) == 0 || strings.TrimSpace(string(raw)) == "" || strings.TrimSpace(string(raw)) == "null" {
		return nil, nil
	}
	var decoded map[string]any
	if err := json.Unmarshal(raw, &decoded); err != nil {
		return nil, err
	}
	if len(decoded) == 0 {
		return nil, nil
	}
	return decoded, nil
}

// ConvertToJSONMap converts an arbitrary JSON-compatible object to a detached
// map. Existing maps are shallow-copied so callers can safely apply
// persistence overlays without mutating their source value.
func ConvertToJSONMap(value any) (map[string]any, error) {
	if value == nil {
		return nil, nil
	}
	if object, ok := value.(map[string]any); ok {
		cloned := make(map[string]any, len(object))
		for key, item := range object {
			cloned[key] = item
		}
		return cloned, nil
	}
	raw, err := json.Marshal(value)
	if err != nil {
		return nil, err
	}
	var object map[string]any
	if err := json.Unmarshal(raw, &object); err != nil {
		return nil, err
	}
	return object, nil
}

// TaskConversationProjectionJSON is the narrow stored projection read by the
// task conversation list. The containing projection object may carry
// additional fields owned by other conversation kinds, so decoding remains
// intentionally forward-compatible with unknown fields.
type TaskConversationProjectionJSON struct {
	Preview        string `json:"preview"`
	LastAuthorName string `json:"lastAuthorName"`
}

func UnmarshalTaskConversationProjection(raw []byte) (TaskConversationProjectionJSON, error) {
	var value TaskConversationProjectionJSON
	if len(raw) == 0 {
		return value, nil
	}
	if err := json.Unmarshal(raw, &value); err != nil {
		return TaskConversationProjectionJSON{}, fmt.Errorf("decode task conversation projection: %w", err)
	}
	return value, nil
}

// TaskSnapshotShadowV1JSON is the tagged persistence representation of the
// Task-only unread snapshot rolling-deploy fence. Row identity remains in the
// containing table.
type TaskSnapshotShadowV1JSON struct {
	ScopeType                  string    `json:"scopeType"`
	MessageUnreadCount         int64     `json:"messageUnreadCount"`
	ActivityUnreadCount        int64     `json:"activityUnreadCount"`
	ManualUnread               bool      `json:"manualUnread"`
	LastMessageReadSeq         int64     `json:"lastMessageReadSeq"`
	LastActivityReadSeq        int64     `json:"lastActivityReadSeq"`
	EffectiveFromSeq           int64     `json:"effectiveFromSeq"`
	LatestMessageAccountedSeq  int64     `json:"latestMessageAccountedSeq"`
	LatestActivityAccountedSeq int64     `json:"latestActivityAccountedSeq"`
	NotificationState          string    `json:"notificationState"`
	MembershipVisibility       string    `json:"membershipVisibility"`
	ScopeMessageGeneration     int64     `json:"scopeMessageGeneration"`
	ScopeActivityGeneration    int64     `json:"scopeActivityGeneration"`
	MembershipGeneration       int64     `json:"membershipGeneration"`
	NotificationGeneration     int64     `json:"notificationGeneration"`
	ManualUnreadGeneration     int64     `json:"manualUnreadGeneration"`
	RewindGeneration           int64     `json:"rewindGeneration"`
	UpdatedAt                  time.Time `json:"updatedAt"`
}

func MarshalTaskSnapshotShadowV1(value TaskSnapshotShadowV1JSON) ([]byte, error) {
	payload, err := json.Marshal(value)
	if err != nil {
		return nil, fmt.Errorf("encode Task unread snapshot shadow: %w", err)
	}
	return payload, nil
}

func UnmarshalTaskSnapshotShadowV1(raw []byte) (TaskSnapshotShadowV1JSON, error) {
	decoder := json.NewDecoder(bytes.NewReader(raw))
	decoder.DisallowUnknownFields()

	var value TaskSnapshotShadowV1JSON
	if err := decoder.Decode(&value); err != nil {
		return TaskSnapshotShadowV1JSON{}, fmt.Errorf("decode Task unread snapshot shadow: %w", err)
	}
	var trailing any
	switch err := decoder.Decode(&trailing); {
	case err == io.EOF:
		return value, nil
	case err == nil:
		return TaskSnapshotShadowV1JSON{}, fmt.Errorf("decode Task unread snapshot shadow: trailing JSON value")
	default:
		return TaskSnapshotShadowV1JSON{}, fmt.Errorf("decode Task unread snapshot shadow: %w", err)
	}
}

func MarshalTaskStringListString(values []string) (string, error) {
	if values == nil {
		values = []string{}
	}
	payload, err := json.Marshal(values)
	if err != nil {
		return "", err
	}
	return string(payload), nil
}

func NormalizeJSONDocument(value string, fallback string) ([]byte, bool) {
	trimmed := strings.TrimSpace(value)
	if trimmed == "" {
		trimmed = strings.TrimSpace(fallback)
	}
	if trimmed == "" {
		trimmed = `{}`
	}
	if !json.Valid([]byte(trimmed)) {
		return nil, false
	}
	return []byte(trimmed), true
}

// MarshalJSONObject encodes a typed value for a store-owned DB JSON column.
//
// API/browser wire JSON must be defined in internal/api/dto and converted before
// it reaches store. Store business packages should use this helper only for
// persistence JSON columns that belong to the store model.
func MarshalJSONObject(label string, value any) ([]byte, error) {
	payload, err := json.Marshal(value)
	if err != nil {
		return nil, fmt.Errorf("marshal %s JSON object: %w", strings.TrimSpace(label), err)
	}
	return payload, nil
}

// MarshalJSONString encodes a typed value for a string-backed DB JSON column.
func MarshalJSONString(label string, value any) (string, error) {
	payload, err := MarshalJSONObject(label, value)
	if err != nil {
		return "", err
	}
	return string(payload), nil
}

// UnmarshalJSONObject decodes a store-owned DB JSON column into a typed value.
func UnmarshalJSONObject(label string, raw []byte, target any) error {
	trimmed := bytes.TrimSpace(raw)
	if len(trimmed) == 0 || bytes.Equal(trimmed, []byte("null")) {
		return nil
	}
	if err := json.Unmarshal(trimmed, target); err != nil {
		return fmt.Errorf("decode %s JSON object: %w", strings.TrimSpace(label), err)
	}
	return nil
}

// UnmarshalJSONString decodes a string-backed store-owned DB JSON column.
func UnmarshalJSONString(label string, raw string, target any) error {
	return UnmarshalJSONObject(label, []byte(strings.TrimSpace(raw)), target)
}

// UnwrapTiDBOpaqueJSONText repairs JSON text stored as a TiDB opaque
// string/BLOB scalar. Callers must opt in only at a known compatibility seam
// and must still decode the returned document into its required object/array
// shape. The bridge can be removed after every deployed writer casts JSON
// parameters and no older pending record can remain within its recovery window.
func UnwrapTiDBOpaqueJSONText(label string, raw []byte) ([]byte, error) {
	trimmed := bytes.TrimSpace(raw)
	if len(trimmed) == 0 || trimmed[0] != '"' {
		return trimmed, nil
	}

	var encoded string
	const prefix = "base64:type"
	if err := json.Unmarshal(trimmed, &encoded); err != nil || !strings.HasPrefix(encoded, prefix) {
		return trimmed, nil
	}
	typeCodeText, payload, ok := strings.Cut(strings.TrimPrefix(encoded, prefix), ":")
	if !ok || typeCodeText == "" || payload == "" {
		return nil, fmt.Errorf("decode %s TiDB opaque JSON text: malformed envelope", strings.TrimSpace(label))
	}
	typeCode, err := strconv.Atoi(typeCodeText)
	if err != nil || !isTiDBJSONTextType(typeCode) {
		return nil, fmt.Errorf("decode %s TiDB opaque JSON text: unsupported field type", strings.TrimSpace(label))
	}
	decoded, err := base64.StdEncoding.Strict().DecodeString(payload)
	if err != nil {
		return nil, fmt.Errorf("decode %s TiDB opaque JSON text: invalid base64 payload", strings.TrimSpace(label))
	}
	decoded = bytes.TrimSpace(decoded)
	if !json.Valid(decoded) {
		return nil, fmt.Errorf("decode %s TiDB opaque JSON text: invalid JSON payload", strings.TrimSpace(label))
	}
	return decoded, nil
}

func isTiDBJSONTextType(typeCode int) bool {
	switch typeCode {
	case 15, 249, 250, 251, 252, 253, 254: // VARCHAR, BLOB/TEXT families, VAR_STRING, STRING.
		return true
	default:
		return false
	}
}

// NormalizeJSONObject returns a deterministic JSON encoding for a DB JSON column.
func NormalizeJSONObject(label string, raw []byte, target any) ([]byte, error) {
	if err := UnmarshalJSONObject(label, raw, target); err != nil {
		return nil, err
	}
	return MarshalJSONObject(label, target)
}

// IsValidJSON reports whether raw is syntactically valid JSON.
func IsValidJSON(raw []byte) bool {
	return json.Valid(bytes.TrimSpace(raw))
}

// JSONDocumentsEqual compares DB JSON documents by decoded value while
// preserving byte equality as the fallback for malformed legacy payloads.
func JSONDocumentsEqual(left []byte, right []byte) bool {
	left = bytes.TrimSpace(left)
	right = bytes.TrimSpace(right)
	if len(left) == 0 || len(right) == 0 {
		return len(left) == 0 && len(right) == 0
	}
	if !json.Valid(left) || !json.Valid(right) {
		return bytes.Equal(left, right)
	}
	leftDocument := decodeJSONDocumentWithNumbers(left)
	rightDocument := decodeJSONDocumentWithNumbers(right)
	return reflect.DeepEqual(leftDocument, rightDocument)
}

func decodeJSONDocumentWithNumbers(raw []byte) any {
	decoder := json.NewDecoder(bytes.NewReader(raw))
	decoder.UseNumber()
	var document any
	_ = decoder.Decode(&document)
	return document
}
