package app

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

	"github.com/tidbcloud/AgentTeam/internal/coredb"
	"github.com/tidbcloud/AgentTeam/internal/store/access"
	appinstallation "github.com/tidbcloud/AgentTeam/internal/store/app/installation"
	"github.com/tidbcloud/AgentTeam/internal/store/model"
	"github.com/tidbcloud/AgentTeam/internal/store/serviceactor"
)

const (
	installationExtensionNameConflictReason = "extension_name_conflict"
	installationExtensionManagerDisabled    = "disabled_by_manager"
)

// InstallationExtensionView combines one App Definition's allowed Extension
// entry with the Workspace Installation's optional selection. Definition
// metadata is immutable and secret-free; selection identity/state is empty
// until the Workspace explicitly selects the entry.
type InstallationExtensionView struct {
	ID                    string
	WorkspaceID           string
	ServiceDefinitionID   string
	ServiceInstallationID string
	ExtensionName         string
	DefinitionDomain      string
	DefinitionVersion     int
	DefinitionDigest      string
	DefinitionDisplayName string
	DefaultName           string
	DefaultEnabled        bool
	Selected              bool
	Status                string
	DisabledReason        string
	SelectionSource       string
	CreatedAt             time.Time
	UpdatedAt             time.Time
}

// ListInstallationExtensionsForUser returns the configured Extension
// selections under one App Installation after applying the existing App
// manager authorization boundary.
func (store *Store) ListInstallationExtensionsForUser(
	ctx context.Context,
	userID string,
	workspaceID string,
	installationID string,
) ([]InstallationExtensionView, error) {
	_, summary, err := store.authorizeInstallationExtensionManagement(
		ctx,
		userID,
		workspaceID,
		installationID,
	)
	if err != nil {
		return nil, err
	}
	selections, err := store.serviceActorStore().ListInstallationExtensions(
		ctx,
		summary.Installation.WorkspaceID,
		summary.Installation.ID,
	)
	if err != nil {
		return nil, err
	}
	capability, err := store.loadInstallationExtensionCapability(ctx, summary.Definition.ID)
	if err != nil {
		return nil, err
	}
	return installationExtensionViews(capability, selections), nil
}

// AddInstallationExtensionForUser selects one App-advertised Extension
// Definition. The Definition identity always comes from the App capability;
// callers can choose only the Agent-visible name.
func (store *Store) AddInstallationExtensionForUser(
	ctx context.Context,
	userID string,
	workspaceID string,
	installationID string,
	definitionDomain string,
	extensionName string,
) (serviceactor.InstallationExtension, error) {
	workspaceMember, summary, err := store.authorizeInstallationExtensionManagement(
		ctx,
		userID,
		workspaceID,
		installationID,
	)
	if err != nil {
		return serviceactor.InstallationExtension{}, err
	}
	definitionDomain = strings.TrimSpace(definitionDomain)
	if definitionDomain == "" {
		return serviceactor.InstallationExtension{}, badRequest("definitionDomain is required")
	}
	requestedExtensionName := strings.TrimSpace(extensionName)

	var selection serviceactor.InstallationExtension
	err = coredb.WithTx(ctx, store.db, func(tx coredb.Tx) error {
		if err := appinstallation.LockAppInstallationMutationScope(ctx, tx, summary.Definition.ID); err != nil {
			return err
		}
		currentMember, err := store.revalidateInstallationManagementForUpdate(
			ctx,
			tx,
			strings.TrimSpace(userID),
			summary.Installation.WorkspaceID,
			workspaceMember.Member.ID,
			summary.Installation.ID,
			summary.Definition.ID,
		)
		if err != nil {
			return err
		}
		txStore := store.serviceActorStore().InTx(tx)
		installation, err := txStore.LoadInstallationForUpdate(ctx, summary.Installation.ID)
		if err != nil {
			return err
		}
		if installation.WorkspaceID != summary.Installation.WorkspaceID ||
			installation.ServiceDefinitionID != summary.Definition.ID ||
			installation.ServiceKind != serviceactor.KindApp ||
			installation.Status != serviceactor.StatusActive {
			return conflict("App installation changed before Extension selection update.")
		}
		selection, err = store.addInstallationExtensionInTx(
			ctx,
			tx,
			txStore,
			installation,
			currentMember.Member.ID,
			definitionDomain,
			requestedExtensionName,
			serviceactor.InstallationExtensionSourceUser,
			time.Now().UTC(),
		)
		return err
	})
	if err != nil {
		return serviceactor.InstallationExtension{}, err
	}
	return selection, nil
}

func (store *Store) addInstallationExtensionInTx(
	ctx context.Context,
	tx coredb.Tx,
	txStore *serviceactor.TxStore,
	installation serviceactor.Installation,
	createdByMemberID string,
	definitionDomain string,
	requestedExtensionName string,
	selectionSource string,
	now time.Time,
) (serviceactor.InstallationExtension, error) {
	capabilities, err := txStore.ListCapabilities(
		ctx,
		installation.ServiceDefinitionID,
	)
	if err != nil {
		return serviceactor.InstallationExtension{}, err
	}
	currentCapability, err := extensionCapabilityConfigFromCapabilities(
		capabilities,
	)
	if err != nil {
		return serviceactor.InstallationExtension{}, err
	}
	entry, found := extensionCapabilityEntryByDomain(
		currentCapability,
		definitionDomain,
	)
	if !found {
		return serviceactor.InstallationExtension{},
			badRequest("Extension is not allowed by this App")
	}
	effectiveExtensionName := strings.TrimSpace(requestedExtensionName)
	if effectiveExtensionName == "" {
		effectiveExtensionName = entry.DefaultName
	}
	if err := lockInstallationExtensionNameNamespace(
		ctx,
		tx,
		installation.WorkspaceID,
	); err != nil {
		return serviceactor.InstallationExtension{}, err
	}
	existing, err := txStore.ListInstallationExtensions(
		ctx,
		installation.WorkspaceID,
		installation.ID,
	)
	if err != nil {
		return serviceactor.InstallationExtension{}, err
	}
	for _, current := range existing {
		if current.DefinitionDomain == entry.Domain {
			return serviceactor.InstallationExtension{},
				conflict("Extension is already selected for this App installation.")
		}
	}
	selection, err := txStore.UpsertInstallationExtension(
		ctx,
		serviceactor.InstallationExtensionUpsert{
			InstallationExtension: serviceactor.InstallationExtension{
				ID:                    installationExtensionID(installation.ID, entry.Domain),
				WorkspaceID:           installation.WorkspaceID,
				ServiceDefinitionID:   installation.ServiceDefinitionID,
				ServiceInstallationID: installation.ID,
				ExtensionName:         effectiveExtensionName,
				DefinitionDomain:      entry.Domain,
				DefinitionVersion:     entry.Version,
				DefinitionDigest:      entry.DefinitionDigest,
				Status:                serviceactor.StatusActive,
				SelectionSource:       strings.TrimSpace(selectionSource),
				CreatedByMemberID:     strings.TrimSpace(createdByMemberID),
			},
			Now: now,
		},
	)
	if err != nil {
		return serviceactor.InstallationExtension{}, err
	}
	if err := store.RunWorkspaceAppCatalogReconcileHook(
		ctx,
		tx,
		installation.WorkspaceID,
		selection.UpdatedAt,
	); err != nil {
		return serviceactor.InstallationExtension{}, err
	}
	return selection, nil
}

// RenameInstallationExtensionForUser changes the Agent-visible name without
// implicitly enabling a disabled selection.
func (store *Store) RenameInstallationExtensionForUser(
	ctx context.Context,
	userID string,
	workspaceID string,
	installationID string,
	selectionID string,
	extensionName string,
) (serviceactor.InstallationExtension, error) {
	extensionName = strings.TrimSpace(extensionName)
	if extensionName == "" {
		return serviceactor.InstallationExtension{}, badRequest("extensionName is required")
	}
	return store.mutateInstallationExtensionForUser(
		ctx,
		userID,
		workspaceID,
		installationID,
		selectionID,
		func(selection serviceactor.InstallationExtension, now time.Time) serviceactor.InstallationExtension {
			selection.ExtensionName = extensionName
			selection.SelectionSource = serviceactor.InstallationExtensionSourceUser
			if selection.Status == serviceactor.StatusDisabled &&
				selection.DisabledReason == installationExtensionNameConflictReason {
				selection.DisabledReason = installationExtensionManagerDisabled
			}
			selection.UpdatedAt = now
			return selection
		},
	)
}

// EnableInstallationExtensionForUser activates one configured selection. The
// Workspace active-name unique key remains the final concurrency guard.
func (store *Store) EnableInstallationExtensionForUser(
	ctx context.Context,
	userID string,
	workspaceID string,
	installationID string,
	selectionID string,
) (serviceactor.InstallationExtension, error) {
	return store.mutateInstallationExtensionForUser(
		ctx,
		userID,
		workspaceID,
		installationID,
		selectionID,
		func(selection serviceactor.InstallationExtension, now time.Time) serviceactor.InstallationExtension {
			selection.Status = serviceactor.StatusActive
			selection.DisabledReason = ""
			selection.DisabledAt = nil
			selection.SelectionSource = serviceactor.InstallationExtensionSourceUser
			selection.UpdatedAt = now
			return selection
		},
	)
}

// DisableInstallationExtensionForUser disables one configured selection while
// leaving it visible in App settings for a later explicit re-enable.
func (store *Store) DisableInstallationExtensionForUser(
	ctx context.Context,
	userID string,
	workspaceID string,
	installationID string,
	selectionID string,
) (serviceactor.InstallationExtension, error) {
	return store.mutateInstallationExtensionForUser(
		ctx,
		userID,
		workspaceID,
		installationID,
		selectionID,
		func(selection serviceactor.InstallationExtension, now time.Time) serviceactor.InstallationExtension {
			selection.Status = serviceactor.StatusDisabled
			selection.DisabledReason = installationExtensionManagerDisabled
			selection.DisabledAt = &now
			selection.SelectionSource = serviceactor.InstallationExtensionSourceUser
			selection.UpdatedAt = now
			return selection
		},
	)
}

func (store *Store) authorizeInstallationExtensionManagement(
	ctx context.Context,
	userID string,
	workspaceID string,
	installationID string,
) (access.HumanWorkspaceMember, serviceactor.InstallationSummary, error) {
	workspaceID = strings.TrimSpace(workspaceID)
	installationID = strings.TrimSpace(installationID)
	workspaceMember, err := store.requireWorkspaceAccess(ctx, strings.TrimSpace(userID), workspaceID)
	if err != nil {
		return access.HumanWorkspaceMember{}, serviceactor.InstallationSummary{}, err
	}
	if installationID == "" {
		return access.HumanWorkspaceMember{}, serviceactor.InstallationSummary{}, badRequest("installationId is required")
	}
	summary, err := store.serviceActorStore().LoadInstallationSummaryByKind(
		ctx,
		workspaceID,
		installationID,
		serviceactor.KindApp,
	)
	if err != nil {
		return access.HumanWorkspaceMember{}, serviceactor.InstallationSummary{}, err
	}
	if summary.Installation.Status != serviceactor.StatusActive {
		return access.HumanWorkspaceMember{}, serviceactor.InstallationSummary{}, ErrNotFound
	}
	if err := store.authorizeInstallationManagementFromSummary(
		ctx,
		store.db,
		workspaceID,
		workspaceMember,
		summary,
	); err != nil {
		return access.HumanWorkspaceMember{}, serviceactor.InstallationSummary{}, err
	}
	return workspaceMember, summary, nil
}

type installationExtensionMutation func(
	selection serviceactor.InstallationExtension,
	now time.Time,
) serviceactor.InstallationExtension

func (store *Store) mutateInstallationExtensionForUser(
	ctx context.Context,
	userID string,
	workspaceID string,
	installationID string,
	selectionID string,
	mutate installationExtensionMutation,
) (serviceactor.InstallationExtension, error) {
	workspaceMember, summary, err := store.authorizeInstallationExtensionManagement(
		ctx,
		userID,
		workspaceID,
		installationID,
	)
	if err != nil {
		return serviceactor.InstallationExtension{}, err
	}
	selectionID = strings.TrimSpace(selectionID)
	if selectionID == "" {
		return serviceactor.InstallationExtension{}, badRequest("selectionId is required")
	}

	var updated serviceactor.InstallationExtension
	err = coredb.WithTx(ctx, store.db, func(tx coredb.Tx) error {
		if err := appinstallation.LockAppInstallationMutationScope(ctx, tx, summary.Definition.ID); err != nil {
			return err
		}
		if _, err := store.revalidateInstallationManagementForUpdate(
			ctx,
			tx,
			strings.TrimSpace(userID),
			summary.Installation.WorkspaceID,
			workspaceMember.Member.ID,
			summary.Installation.ID,
			summary.Definition.ID,
		); err != nil {
			return err
		}

		txStore := store.serviceActorStore().InTx(tx)
		installation, err := txStore.LoadInstallationForUpdate(ctx, summary.Installation.ID)
		if err != nil {
			return err
		}
		if installation.WorkspaceID != summary.Installation.WorkspaceID ||
			installation.ServiceDefinitionID != summary.Definition.ID ||
			installation.ServiceKind != serviceactor.KindApp ||
			installation.Status != serviceactor.StatusActive {
			return conflict("App installation changed before Extension selection update.")
		}
		if err := lockInstallationExtensionNameNamespace(ctx, tx, installation.WorkspaceID); err != nil {
			return err
		}

		selections, err := txStore.ListInstallationExtensions(ctx, installation.WorkspaceID, installation.ID)
		if err != nil {
			return err
		}
		current, found := installationExtensionByID(selections, selectionID)
		if !found {
			return ErrNotFound
		}
		candidate := mutate(current, time.Now().UTC())
		if candidate.Status == serviceactor.StatusActive {
			capabilities, err := txStore.ListCapabilities(ctx, installation.ServiceDefinitionID)
			if err != nil {
				return err
			}
			capability, err := extensionCapabilityConfigFromCapabilities(capabilities)
			if err != nil {
				return err
			}
			entry, found := extensionCapabilityEntryByDomain(capability, candidate.DefinitionDomain)
			if !found ||
				entry.Version != candidate.DefinitionVersion ||
				entry.DefinitionDigest != candidate.DefinitionDigest {
				return conflict("Extension Definition is no longer allowed by this App.")
			}
		}
		updated, err = txStore.UpsertInstallationExtension(ctx, serviceactor.InstallationExtensionUpsert{
			InstallationExtension: candidate,
			Now:                   candidate.UpdatedAt,
		})
		if err != nil {
			return err
		}
		if current.Status == serviceactor.StatusActive || updated.Status == serviceactor.StatusActive {
			return store.RunWorkspaceAppCatalogReconcileHook(
				ctx,
				tx,
				installation.WorkspaceID,
				updated.UpdatedAt,
			)
		}
		return nil
	})
	if err != nil {
		return serviceactor.InstallationExtension{}, err
	}
	return updated, nil
}

func builtinExtensionCapabilityConfig(serviceDefinitionID string) (model.ExtensionCapabilityConfig, bool) {
	if strings.TrimSpace(serviceDefinitionID) == "" {
		return model.ExtensionCapabilityConfig{}, false
	}
	return appinstallation.BuiltinExtensionCapabilityConfig(serviceDefinitionID)
}

func (store *Store) loadInstallationExtensionCapability(
	ctx context.Context,
	serviceDefinitionID string,
) (model.ExtensionCapabilityConfig, error) {
	capabilities, err := store.serviceActorStore().ListCapabilities(ctx, serviceDefinitionID)
	if err != nil {
		return model.ExtensionCapabilityConfig{}, err
	}
	return extensionCapabilityConfigFromCapabilities(capabilities)
}

func extensionCapabilityConfigFromCapabilities(
	capabilities []serviceactor.Capability,
) (model.ExtensionCapabilityConfig, error) {
	for _, capability := range capabilities {
		if capability.CapabilityKey != serviceactor.CapabilityExtensions || !capability.DefaultEnabled {
			continue
		}
		config, err := model.DecodeExtensionCapabilityConfig(capability.Config)
		if err != nil {
			return model.ExtensionCapabilityConfig{}, fmt.Errorf("decode App Extension capability: %w", err)
		}
		return config, nil
	}
	return model.ExtensionCapabilityConfig{Extensions: []model.ExtensionCapabilityEntry{}}, nil
}

func extensionCapabilityEntryByDomain(
	config model.ExtensionCapabilityConfig,
	definitionDomain string,
) (model.ExtensionCapabilityEntry, bool) {
	definitionDomain = strings.TrimSpace(definitionDomain)
	for _, entry := range config.Extensions {
		if entry.Domain == definitionDomain {
			return entry, true
		}
	}
	return model.ExtensionCapabilityEntry{}, false
}

func installationExtensionViews(
	config model.ExtensionCapabilityConfig,
	selections []serviceactor.InstallationExtension,
) []InstallationExtensionView {
	selectedByDomain := make(map[string]serviceactor.InstallationExtension, len(selections))
	for _, selection := range selections {
		selectedByDomain[selection.DefinitionDomain] = selection
	}
	views := make([]InstallationExtensionView, 0, len(config.Extensions)+len(selections))
	seenDomains := make(map[string]struct{}, len(config.Extensions))
	for _, entry := range config.Extensions {
		view := InstallationExtensionView{
			DefinitionDomain:      entry.Domain,
			DefinitionVersion:     entry.Version,
			DefinitionDigest:      entry.DefinitionDigest,
			DefinitionDisplayName: entry.Domain,
			DefaultName:           entry.DefaultName,
			DefaultEnabled:        entry.DefaultEnabled,
		}
		if selection, found := selectedByDomain[entry.Domain]; found {
			applyInstallationExtensionSelection(&view, selection)
		}
		views = append(views, view)
		seenDomains[entry.Domain] = struct{}{}
	}
	for _, selection := range selections {
		if _, found := seenDomains[selection.DefinitionDomain]; found {
			continue
		}
		view := InstallationExtensionView{
			DefinitionDomain:      selection.DefinitionDomain,
			DefinitionVersion:     selection.DefinitionVersion,
			DefinitionDigest:      selection.DefinitionDigest,
			DefinitionDisplayName: selection.DefinitionDomain,
		}
		applyInstallationExtensionSelection(&view, selection)
		views = append(views, view)
	}
	return views
}

func applyInstallationExtensionSelection(
	view *InstallationExtensionView,
	selection serviceactor.InstallationExtension,
) {
	view.ID = selection.ID
	view.WorkspaceID = selection.WorkspaceID
	view.ServiceDefinitionID = selection.ServiceDefinitionID
	view.ServiceInstallationID = selection.ServiceInstallationID
	view.ExtensionName = selection.ExtensionName
	view.DefinitionVersion = selection.DefinitionVersion
	view.DefinitionDigest = selection.DefinitionDigest
	view.Selected = true
	view.Status = selection.Status
	view.DisabledReason = selection.DisabledReason
	view.SelectionSource = selection.SelectionSource
	view.CreatedAt = selection.CreatedAt
	view.UpdatedAt = selection.UpdatedAt
}

func lockInstallationExtensionNameNamespace(
	ctx context.Context,
	queryer coredb.QueryRower,
	workspaceID string,
) error {
	var lockedWorkspaceID string
	if err := queryer.QueryRow(ctx, `
SELECT id
FROM workspaces
WHERE id = $1
LIMIT 1
FOR UPDATE
`, strings.TrimSpace(workspaceID)).Scan(&lockedWorkspaceID); err != nil {
		if coredb.IsNotFound(err) {
			return ErrNotFound
		}
		return fmt.Errorf("lock Workspace Extension name namespace: %w", err)
	}
	return nil
}

func installationExtensionID(installationID string, definitionDomain string) string {
	sum := sha256.Sum256([]byte(
		"service-installation-extension/" +
			strings.TrimSpace(installationID) +
			"/" +
			strings.TrimSpace(definitionDomain),
	))
	return "svcie_" + hex.EncodeToString(sum[:])[:32]
}

func installationExtensionByID(
	selections []serviceactor.InstallationExtension,
	selectionID string,
) (serviceactor.InstallationExtension, bool) {
	selectionID = strings.TrimSpace(selectionID)
	for _, selection := range selections {
		if selection.ID == selectionID {
			return selection, true
		}
	}
	return serviceactor.InstallationExtension{}, false
}
