package app

import (
	"context"
	"fmt"
	"strings"
	"time"

	"github.com/tidbcloud/AgentTeam/internal/core/identifiers"
	"github.com/tidbcloud/AgentTeam/internal/coredb"
	"github.com/tidbcloud/AgentTeam/internal/store/storeerr"
)

type TargetAppBindingRecord struct {
	ID          string
	WorkspaceID string
	TargetKind  string
	TargetRef   string

	DeliveryScopeID          string
	PermissionCheckScopeID   string
	AuthoritySubjectMemberID string
	DeliveryContract         []byte
	AppID                    string
	AppInstallationID        string
	AppMemberID              string
	Status                   string
	DisabledReason           string
	CreatedByMemberID        string
	CreatedByActorType       string
	CreatedByActorID         string
	CreatedVia               string
	ScopeSnapshot            []byte
	Metadata                 []byte
	CreatedAt                time.Time
	UpdatedAt                time.Time
}

func (store *Store) upsertTargetAppBinding(ctx context.Context, tx coredb.Tx, workspaceID string, requestedBindingID string, installation providerWatchInstallation, actor ProviderWatchActor, target ProviderWatchTarget, createdVia string, scopeSnapshot []byte, metadata []byte) (TargetAppBindingRecord, error) {
	serviceMemberID, err := loadServiceMemberIDForInstallation(ctx, tx, workspaceID, installation.ID, installation.MemberID)
	if err != nil {
		return TargetAppBindingRecord{}, err
	}
	if binding, found, err := loadCurrentTargetAppBindingForUpdate(ctx, tx, workspaceID, target.TargetKind, target.TargetRef, installation.ID); err != nil {
		return TargetAppBindingRecord{}, err
	} else if found {
		if binding.Status == TargetAppBindingStatusActive {
			return binding, nil
		}
		if binding.Status != TargetAppBindingStatusDisabled {
			return TargetAppBindingRecord{}, storeerr.Conflict("target_app_binding_unavailable")
		}
		now := time.Now().UTC()
		if _, err := tx.Exec(ctx, `
UPDATE service_target_bindings
SET status = 'active',
    disabled_reason = '',
    delivery_scope_id = $4,
    permission_check_scope_id = $5,
    authority_subject_member_id = $6,
    delivery_contract = $7,
    scope_snapshot = $8,
    metadata = $9,
    updated_at = $10
WHERE id = $1 AND workspace_id = $2 AND service_installation_id = $3 AND removed_at IS NULL
`, binding.ID, workspaceID, installation.ID, target.DeliveryScopeID, target.PermissionCheckScopeID, serviceEventAuthoritySubjectMemberID(actor, target), serviceEventDeliveryContractJSON(actor, target), scopeSnapshot, metadata, now); err != nil {
			return TargetAppBindingRecord{}, fmt.Errorf("restore target app binding: %w", err)
		}
		return loadTargetAppBindingByID(ctx, tx, binding.ID)
	}

	bindingID := strings.TrimSpace(requestedBindingID)
	if bindingID == "" {
		bindingID, err = identifiers.NewEntityID("tab")
		if err != nil {
			return TargetAppBindingRecord{}, fmt.Errorf("generate target app binding id: %w", err)
		}
	}
	now := time.Now().UTC()
	_, err = tx.Exec(ctx, `
INSERT INTO service_target_bindings (
    id, workspace_id, service_definition_id, service_installation_id,
    service_member_id, service_kind, target_kind, target_ref, delivery_scope_id,
    permission_check_scope_id, authority_subject_member_id, delivery_contract,
    status, disabled_reason, created_by_member_id, created_by_actor_type,
    created_by_actor_id, created_via, scope_snapshot, metadata, created_at,
    updated_at, active_binding_key
) VALUES (
    $1, $2, $3, $4,
    $5, 'app', $6, $7, $8,
    $9, $10, $11,
    'active', '', $12, $13,
    $14, $15, $16, $17, $18,
    $18, $19
)
`, bindingID, workspaceID, installation.AppID, installation.ID, serviceMemberID, target.TargetKind, target.TargetRef, target.DeliveryScopeID, target.PermissionCheckScopeID, serviceEventAuthoritySubjectMemberID(actor, target), serviceEventDeliveryContractJSON(actor, target), serviceEventActorPrincipalMemberID(actor), actor.ActorType, actor.ActorID, createdVia, scopeSnapshot, metadata, now, providerWatchKey(workspaceID, target.TargetKind, target.TargetRef, installation.ID))
	if err != nil {
		if coredb.IsUniqueViolation(err) {
			binding, found, loadErr := loadCurrentTargetAppBindingForUpdate(ctx, tx, workspaceID, target.TargetKind, target.TargetRef, installation.ID)
			if loadErr != nil {
				return TargetAppBindingRecord{}, loadErr
			}
			if found && binding.Status == TargetAppBindingStatusActive {
				return binding, nil
			}
			if found && binding.Status == TargetAppBindingStatusDisabled {
				return store.upsertTargetAppBinding(ctx, tx, workspaceID, requestedBindingID, installation, actor, target, createdVia, scopeSnapshot, metadata)
			}
		}
		return TargetAppBindingRecord{}, fmt.Errorf("insert target app binding: %w", err)
	}
	return loadTargetAppBindingByID(ctx, tx, bindingID)
}

func loadServiceMemberIDForInstallation(ctx context.Context, queryer coredb.QueryRower, workspaceID string, installationID string, memberID string) (string, error) {
	var serviceMemberID string
	err := queryer.QueryRow(ctx, `
SELECT id
FROM service_members
WHERE workspace_id = $1
  AND service_installation_id = $2
  AND member_id = $3
LIMIT 1
`, strings.TrimSpace(workspaceID), strings.TrimSpace(installationID), strings.TrimSpace(memberID)).Scan(&serviceMemberID)
	if err != nil {
		return "", fmt.Errorf("load service member for target app binding: %w", err)
	}
	return strings.TrimSpace(serviceMemberID), nil
}

func loadCurrentTargetAppBindingForUpdate(ctx context.Context, queryer coredb.Queryer, workspaceID string, targetKind string, targetRef string, installationID string) (TargetAppBindingRecord, bool, error) {
	row := queryer.QueryRow(ctx, `
SELECT binding.id, binding.workspace_id, binding.target_kind, binding.target_ref, binding.delivery_scope_id,
       binding.permission_check_scope_id, binding.authority_subject_member_id, binding.delivery_contract,
       binding.service_definition_id, binding.service_installation_id, COALESCE(service_member.member_id, binding.service_member_id),
       binding.status, binding.disabled_reason, binding.created_by_member_id, binding.created_by_actor_type,
       binding.created_by_actor_id, binding.created_via, binding.scope_snapshot, binding.metadata, binding.created_at,
       binding.updated_at
FROM service_target_bindings binding
LEFT JOIN service_members service_member
  ON service_member.id = binding.service_member_id
WHERE binding.workspace_id = $1
  AND binding.target_kind = $2
  AND binding.target_ref = $3
  AND binding.service_installation_id = $4
  AND binding.removed_at IS NULL
LIMIT 1
FOR UPDATE
`, workspaceID, targetKind, targetRef, installationID)
	binding, err := scanTargetAppBindingRecord(row)
	if err != nil {
		if coredb.IsNotFound(err) {
			return TargetAppBindingRecord{}, false, nil
		}
		return TargetAppBindingRecord{}, false, err
	}
	return binding, true, nil
}

func loadTargetAppBindingByID(ctx context.Context, queryer coredb.QueryRower, bindingID string) (TargetAppBindingRecord, error) {
	row := queryer.QueryRow(ctx, `
SELECT binding.id, binding.workspace_id, binding.target_kind, binding.target_ref, binding.delivery_scope_id,
       binding.permission_check_scope_id, binding.authority_subject_member_id, binding.delivery_contract,
       binding.service_definition_id, binding.service_installation_id, COALESCE(service_member.member_id, binding.service_member_id),
       binding.status, binding.disabled_reason, binding.created_by_member_id, binding.created_by_actor_type,
       binding.created_by_actor_id, binding.created_via, binding.scope_snapshot, binding.metadata, binding.created_at,
       binding.updated_at
FROM service_target_bindings binding
LEFT JOIN service_members service_member
  ON service_member.id = binding.service_member_id
WHERE binding.id = $1
`, bindingID)
	return scanTargetAppBindingRecord(row)
}

func scanTargetAppBindingRecord(row coredb.Row) (TargetAppBindingRecord, error) {
	var binding TargetAppBindingRecord
	if err := row.Scan(
		&binding.ID,
		&binding.WorkspaceID,
		&binding.TargetKind,
		&binding.TargetRef,
		&binding.DeliveryScopeID,
		&binding.PermissionCheckScopeID,
		&binding.AuthoritySubjectMemberID,
		&binding.DeliveryContract,
		&binding.AppID,
		&binding.AppInstallationID,
		&binding.AppMemberID,
		&binding.Status,
		&binding.DisabledReason,
		&binding.CreatedByMemberID,
		&binding.CreatedByActorType,
		&binding.CreatedByActorID,
		&binding.CreatedVia,
		&binding.ScopeSnapshot,
		&binding.Metadata,
		&binding.CreatedAt,
		&binding.UpdatedAt,
	); err != nil {
		return TargetAppBindingRecord{}, err
	}
	return binding, nil
}

// providerWatchInstallation is the provider-watch write boundary's local
// installation view. Canonical service-event creation uses the Service Actor
// store directly; this type remains for the decomposed binding helper.
type providerWatchInstallation struct {
	ID          string
	AppID       string
	WorkspaceID string
	MemberID    string
	Handle      string
	Enabled     bool
}

func providerWatchKey(workspaceID string, targetKind string, targetRef string, installationID string) string {
	return pullRequestSubscriptionKey("service-event-target", workspaceID, targetKind, targetRef, installationID)
}
