package approvalapp

import (
	"context"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"strings"
	"time"

	"github.com/tidbcloud/AgentTeam/internal/coredb"
	"github.com/tidbcloud/AgentTeam/internal/store/serviceactor"
)

const (
	approvalAppDisplayName = "Approval"
	approvalAppHandle      = "agentteam-approval"
	approvalAppVersion     = "1.0.0"

	// These historical "bot" namespace strings are immutable persisted
	// identity material. Product terminology may change, but changing any of
	// these values would break reconciliation for existing installations.
	approvalAppMemberIDNamespace        = "approval-bot-member"
	approvalAppInstallationIDNamespace  = "approval-bot-installation"
	approvalAppServiceMemberIDNamespace = "approval-bot-service-member"

	// approvalAppInstallationMetadataJSON is the immutable capability snapshot
	// used to authorize this built-in installation at runtime. Reconciliation
	// intentionally rewrites legacy built-in metadata that predates manifest
	// pinning; mutable service-definition capabilities are never installation
	// authority.
	approvalAppInstallationMetadataJSON = `{
		"builtin": true,
		"identity": "agentteam.approval",
		"version": "1.0.0",
		"manifest": {
			"schema_version": "1",
			"slug": "agentteam-approval",
			"name": "Approval",
			"version": "1.0.0",
			"capabilities": {
				"runtime": {
					"events": ["agentteam.app.notification.action"],
					"scopes": [
						"messages.write",
						"app.notifications.write",
						"app.notification_actions.read"
					],
					"notifications": {
						"targets": ["human_dm", "bound_scope"],
						"content": ["message", "interactive_card"]
					}
				}
			}
		}
	}`
)

type Provisioned struct {
	Installation serviceactor.Installation
	Member       serviceactor.Member
}

// Provisioner materializes the singleton built-in Approval for each
// workspace. Every identifier is derived from the workspace, so retries and
// concurrent projectors converge on one installation/member substrate.
type Provisioner struct {
	db     coredb.DB
	actors *serviceactor.Store
}

// MemberDisplayName resolves the public display name for a frozen Workflow
// Run assignee without changing the durable recipient identity.
func (provisioner *Provisioner) MemberDisplayName(
	ctx context.Context,
	workspaceID string,
	memberID string,
) (string, error) {
	if provisioner == nil || provisioner.db == nil {
		return "", fmt.Errorf("approval App provisioner is not configured")
	}
	var displayName string
	if err := provisioner.db.QueryRow(ctx, `
SELECT COALESCE(
    NULLIF(TRIM(user.display_name), ''),
    NULLIF(TRIM(member.display_name), ''),
    member.id
)
FROM members member
LEFT JOIN users user ON user.id = member.user_id
WHERE member.workspace_id = $1 AND member.id = $2
LIMIT 1
`, strings.TrimSpace(workspaceID), strings.TrimSpace(memberID)).Scan(&displayName); err != nil {
		return "", fmt.Errorf("resolve Workflow Run approval member display name: %w", err)
	}
	return strings.TrimSpace(displayName), nil
}

func NewProvisioner(db coredb.DB) *Provisioner {
	return &Provisioner{db: db, actors: serviceactor.New(db)}
}

func (provisioner *Provisioner) EnsureWorkspace(ctx context.Context, workspaceID string, now time.Time) (Provisioned, error) {
	if provisioner == nil || provisioner.db == nil || provisioner.actors == nil {
		return Provisioned{}, fmt.Errorf("approval App provisioner is not configured")
	}
	workspaceID = strings.TrimSpace(workspaceID)
	if workspaceID == "" {
		return Provisioned{}, fmt.Errorf("approval App workspace is required")
	}
	if now.IsZero() {
		now = time.Now().UTC()
	} else {
		now = now.UTC()
	}
	ids := approvalAppIDsForWorkspace(workspaceID)
	var result Provisioned
	err := coredb.WithTx(ctx, provisioner.db, func(tx coredb.Tx) error {
		actors := provisioner.actors.InTx(tx)
		creatorMemberID, err := firstActiveHumanMemberIDForUpdate(ctx, tx, workspaceID)
		if err != nil {
			return err
		}
		if err := actors.EnsureServiceMemberSubstrate(ctx, serviceactor.ServiceMemberSubstrateInsert{
			ID: ids.MemberID, WorkspaceID: workspaceID, DisplayName: approvalAppDisplayName, Now: now,
		}); err != nil {
			return err
		}
		installation, err := actors.UpsertInstallation(ctx, serviceactor.InstallationUpsert{
			ID:          ids.InstallationID,
			WorkspaceID: workspaceID, ServiceDefinitionID: DefinitionID,
			ServiceKind: serviceactor.KindApp, InstanceKey: serviceactor.DefaultInstanceKey,
			Status: serviceactor.StatusActive, InstalledVersion: approvalAppVersion,
			Config: []byte(`{}`), Metadata: []byte(approvalAppInstallationMetadataJSON),
			CreatedByMemberID: creatorMemberID, CreatedByActorType: "internal_service",
			CreatedByActorID: IdentityID, Now: now,
		})
		if err != nil {
			return err
		}
		member, err := actors.UpsertMember(ctx, serviceactor.MemberUpsert{
			ID:          ids.ServiceMemberID,
			WorkspaceID: workspaceID, ServiceDefinitionID: DefinitionID,
			ServiceInstallationID: installation.ID, MemberID: ids.MemberID,
			ServiceKind: serviceactor.KindApp, Handle: approvalAppHandle,
			DisplayName: approvalAppDisplayName, AvatarURL: AvatarURL,
			Status: serviceactor.StatusActive, Now: now,
		})
		if err != nil {
			return err
		}
		if err := actors.SyncGrantScopes(ctx, installation, creatorMemberID, []string{
			"messages.write",
			"app.notifications.write",
			"app.notification_actions.read",
		}, now); err != nil {
			return err
		}
		if err := ensureRecipientPolicy(ctx, tx, actors, installation.ID, now); err != nil {
			return err
		}
		result = Provisioned{Installation: installation, Member: member}
		return nil
	})
	return result, err
}

// ensureRecipientPolicy installs the expanded Approval recipient contract
// without rewriting an existing policy version. Fresh workspaces receive the
// current contract at v2. Existing policy snapshots advance when their digest
// predates the current recipient or business-object set.
func ensureRecipientPolicy(
	ctx context.Context,
	tx coredb.Tx,
	actors *serviceactor.TxStore,
	installationID string,
	now time.Time,
) error {
	var currentVersion int64
	var currentDigest string
	if err := tx.QueryRow(ctx, `
SELECT policy_version, policy_digest
FROM service_grants
WHERE service_installation_id = $1
  AND scope = 'app.notifications.write'
  AND revoked_at IS NULL
ORDER BY policy_version DESC, granted_at DESC, id DESC
LIMIT 1
FOR UPDATE
`, installationID).Scan(&currentVersion, &currentDigest); err != nil {
		return fmt.Errorf("load approval App recipient policy: %w", err)
	}
	switch currentVersion {
	case 1:
		policyJSON, digest, err := recipientPolicyDocument(2)
		if err != nil {
			return err
		}
		_, _, err = actors.EnsureGrantPolicy(
			ctx, installationID, "app.notifications.write", 1,
			policyJSON, digest, nil, now,
		)
		return err
	case 2:
		policyV2, digestV2, err := recipientPolicyDocument(2)
		if err != nil {
			return err
		}
		if strings.TrimSpace(currentDigest) == digestV2 {
			_, _, err = actors.EnsureGrantPolicy(
				ctx, installationID, "app.notifications.write", 1,
				policyV2, digestV2, nil, now,
			)
			return err
		}
		policyV3, digestV3, err := recipientPolicyDocument(3)
		if err != nil {
			return err
		}
		_, _, err = actors.EnsureGrantPolicy(
			ctx, installationID, "app.notifications.write", 2,
			policyV3, digestV3, nil, now,
		)
		return err
	case 3:
		policyJSON, digest, err := recipientPolicyDocument(3)
		if err != nil {
			return err
		}
		if strings.TrimSpace(currentDigest) == digest {
			_, _, err = actors.EnsureGrantPolicy(
				ctx, installationID, "app.notifications.write", 2,
				policyJSON, digest, nil, now,
			)
			return err
		}
		policyV4, digestV4, err := recipientPolicyDocument(4)
		if err != nil {
			return err
		}
		_, _, err = actors.EnsureGrantPolicy(
			ctx, installationID, "app.notifications.write", 3,
			policyV4, digestV4, nil, now,
		)
		return err
	case 4:
		policyJSON, digest, err := recipientPolicyDocument(4)
		if err != nil {
			return err
		}
		if strings.TrimSpace(currentDigest) == digest {
			_, _, err = actors.EnsureGrantPolicy(
				ctx, installationID, "app.notifications.write", 3,
				policyJSON, digest, nil, now,
			)
			return err
		}
		policyV5, digestV5, err := recipientPolicyDocument(5)
		if err != nil {
			return err
		}
		_, _, err = actors.EnsureGrantPolicy(
			ctx, installationID, "app.notifications.write", 4,
			policyV5, digestV5, nil, now,
		)
		return err
	case 5:
		policyJSON, digest, err := recipientPolicyDocument(5)
		if err != nil {
			return err
		}
		_, _, err = actors.EnsureGrantPolicy(
			ctx, installationID, "app.notifications.write", 4,
			policyJSON, digest, nil, now,
		)
		return err
	default:
		return fmt.Errorf("unsupported approval App recipient policy version %d", currentVersion)
	}
}

func recipientPolicyDocument(version int64) ([]byte, string, error) {
	if version <= 0 {
		return nil, "", fmt.Errorf("approval App recipient policy version must be positive")
	}
	base := map[string]any{
		"recipient_policy_version": version,
		"allowed_recipient_basis_kinds": []string{
			RecipientBasisApplicant,
			RecipientBasisAppInstallationApplicant,
			RecipientBasisAppInstallationReviewer,
			RecipientBasisAssignment,
			RecipientBasisWikiGenerationRequester,
			RecipientBasisWorkflowRunAssignee,
		},
		"allowed_business_object_namespaces": []string{BusinessNamespace},
		"allowed_business_object_types": []string{
			BusinessTypeAppInstallationRequest,
			BusinessTypeInstance,
			BusinessTypeWikiGenerationRequest,
			BusinessTypeWorkflowRunApproval,
		},
		"allowed_human_dm_content_kinds": []string{"interactive_card", "message"},
		"organization_policy_ref":        "agentteam.approval",
	}
	deterministicBase, err := json.Marshal(base)
	if err != nil {
		return nil, "", fmt.Errorf("marshal approval App recipient policy: %w", err)
	}
	sum := sha256.Sum256(deterministicBase)
	digest := hex.EncodeToString(sum[:])
	base["policy_digest"] = digest
	document, err := json.Marshal(base)
	if err != nil {
		return nil, "", fmt.Errorf("marshal approval App recipient policy document: %w", err)
	}
	return document, digest, nil
}

type approvalAppIDs struct {
	MemberID        string
	InstallationID  string
	ServiceMemberID string
}

func approvalAppIDsForWorkspace(workspaceID string) approvalAppIDs {
	return approvalAppIDs{
		MemberID:        deterministicID("mem", approvalAppMemberIDNamespace, workspaceID),
		InstallationID:  deterministicID("appinst", approvalAppInstallationIDNamespace, workspaceID),
		ServiceMemberID: deterministicID("svcmem", approvalAppServiceMemberIDNamespace, workspaceID),
	}
}

func deterministicID(prefix, namespace string, parts ...string) string {
	payload := namespace
	for _, part := range parts {
		payload += "\x00" + strings.TrimSpace(part)
	}
	sum := sha256.Sum256([]byte(payload))
	return prefix + "_" + hex.EncodeToString(sum[:16])
}

func firstActiveHumanMemberIDForUpdate(ctx context.Context, queryer coredb.QueryRower, workspaceID string) (string, error) {
	var memberID string
	if err := queryer.QueryRow(ctx, `
SELECT id
FROM members
WHERE workspace_id = $1
  AND type = 'human'
  AND status = 'active'
ORDER BY CASE WHEN workspace_role IN ('owner','admin') THEN 0 ELSE 1 END,
         created_at ASC, id ASC
LIMIT 1
FOR UPDATE
`, workspaceID).Scan(&memberID); err != nil {
		return "", fmt.Errorf("resolve approval App installation creator: %w", err)
	}
	return strings.TrimSpace(memberID), nil
}
