package privateagent

import (
	"context"
	"database/sql"
	"fmt"
	"strings"
	"time"

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

const (
	channelAgentEntryVisibilityPublic  = "public"
	channelAgentEntryVisibilityPrivate = "private"
)

type AgentEntryOwner struct {
	AgentID       string
	AgentMemberID string
	OwnerMemberID string
}

type ActiveChannelAgentEntry struct {
	Visibility    string
	OwnerMemberID sql.NullString
}

// AddChannelPrivateAgentForUser adds an agent to the caller's owner-scoped
// private roster for a channel. The roster entry is stored as an active
// scope_memberships row with visibility=private and owner_member_id set to the
// caller's workspace member; message target resolution treats that row as the
// channel membership basis, while read/routing layers keep the private
// owner-scoped semantics.
func (store *Store) AddChannelPrivateAgentForUser(ctx context.Context, userID string, workspaceID string, channelID string, agentID string) error {
	channelAccess, err := store.requireChannelInvite(ctx, userID, workspaceID, channelID)
	if err != nil {
		return err
	}
	if channelAccess.Channel.Type != "channel" {
		return badRequest("thread channels do not have direct members")
	}

	agent, err := store.loadAgentEntryOwner(ctx, store.db, workspaceID, agentID)
	if err != nil {
		return err
	}
	if strings.TrimSpace(agent.OwnerMemberID) != strings.TrimSpace(channelAccess.WorkspaceMember.Member.ID) {
		return forbidden("only the agent owner can add it as private")
	}

	tx, err := store.db.BeginTx(ctx, coredb.TxOptions{})
	if err != nil {
		return fmt.Errorf("begin add private channel agent tx: %w", err)
	}
	defer rollback(tx, ctx)

	now := time.Now().UTC()
	if err := store.ensurePrivateChannelAgentEntry(ctx, tx, workspaceID, channelID, agent, now); err != nil {
		return err
	}

	if err := tx.Commit(ctx); err != nil {
		return fmt.Errorf("commit add private channel agent: %w", err)
	}
	return nil
}

// RemoveChannelPrivateAgentForUser removes a private roster entry from the
// caller's owner-scoped channel context. It does not delete parent messages or
// mutate parent participants; private-agent authorization is derived from
// private scope memberships and parent-target sparse delivery facts.
func (store *Store) RemoveChannelPrivateAgentForUser(ctx context.Context, userID string, workspaceID string, channelID string, agentID string) error {
	channelAccess, err := store.loadAuthorizedHumanChannelAccess(ctx, store.db, userID, workspaceID, channelID, channelReadAction)
	if err != nil {
		return err
	}
	if channelAccess.Channel.Type != "channel" {
		return badRequest("thread channels do not have direct members")
	}
	if channelAccess.Channel.Slug == "all" {
		return badRequest("cannot remove members from #all")
	}

	agent, err := store.loadAgentEntryOwner(ctx, store.db, workspaceID, agentID)
	if err != nil {
		return err
	}
	if strings.TrimSpace(agent.OwnerMemberID) != strings.TrimSpace(channelAccess.WorkspaceMember.Member.ID) {
		return forbidden("only the agent owner can remove it as private")
	}

	tx, err := store.db.BeginTx(ctx, coredb.TxOptions{})
	if err != nil {
		return fmt.Errorf("begin remove private channel agent tx: %w", err)
	}
	defer rollback(tx, ctx)

	now := time.Now().UTC()
	entry, ok, err := store.loadActiveChannelAgentEntry(ctx, tx, channelID, agent.AgentMemberID)
	if err != nil {
		return err
	}
	if !ok || strings.TrimSpace(entry.Visibility) != channelAgentEntryVisibilityPrivate || strings.TrimSpace(entry.OwnerMemberID.String) != strings.TrimSpace(agent.OwnerMemberID) {
		return ErrNotFound
	}
	if err := store.deletePrivateAgentWakeSubscriptionsForHostChannel(ctx, tx, channelID, agent.AgentMemberID); err != nil {
		return err
	}
	if err := markPrivateAgentScopeMembershipRemoved(ctx, tx, channelID, agent.AgentMemberID, agent.OwnerMemberID, now); err != nil {
		return err
	}

	if err := tx.Commit(ctx); err != nil {
		return fmt.Errorf("commit remove private channel agent: %w", err)
	}
	return nil
}

func (store *Store) deletePrivateAgentWakeSubscriptionsForHostChannel(ctx context.Context, execer coredb.QueryExecer, hostChannelID string, agentMemberID string) error {
	trimmedHostChannelID := strings.TrimSpace(hostChannelID)
	trimmedAgentMemberID := strings.TrimSpace(agentMemberID)
	if trimmedHostChannelID == "" || trimmedAgentMemberID == "" {
		return nil
	}
	if _, err := execer.Exec(ctx, `
DELETE sns
FROM scope_notification_settings sns
JOIN scopes scope
  ON scope.id = sns.scope_id
WHERE sns.member_id = $2
  AND (scope.id = $1 OR scope.parent_scope_id = $1)
`, trimmedHostChannelID, trimmedAgentMemberID); err != nil {
		return fmt.Errorf("delete private agent wake subscriptions: %w", err)
	}
	return nil
}

func (store *Store) loadAgentEntryOwner(ctx context.Context, queryer interface {
	QueryRow(context.Context, string, ...any) coredb.Row
}, workspaceID string, agentID string) (AgentEntryOwner, error) {
	row := queryer.QueryRow(ctx, `
SELECT a.id, a.member_id, owner_member.id
FROM agents a
JOIN machines mac
  ON mac.workspace_id = a.workspace_id
 AND mac.id = a.machine_id
JOIN members owner_member
  ON owner_member.workspace_id = a.workspace_id
 AND owner_member.user_id = mac.created_by_user_id
 AND owner_member.type = 'human'
 AND owner_member.status = 'active'
WHERE a.workspace_id = $1
  AND a.id = $2
  AND a.status <> 'archived'
  AND mac.status = 'active'
`, strings.TrimSpace(workspaceID), strings.TrimSpace(agentID))

	var owner AgentEntryOwner
	if err := row.Scan(&owner.AgentID, &owner.AgentMemberID, &owner.OwnerMemberID); err != nil {
		if coredb.IsNotFound(err) {
			return AgentEntryOwner{}, ErrNotFound
		}
		return AgentEntryOwner{}, fmt.Errorf("load agent entry owner: %w", err)
	}
	return owner, nil
}

func (store *Store) loadActiveChannelAgentEntry(ctx context.Context, queryer interface {
	QueryRow(context.Context, string, ...any) coredb.Row
}, channelID string, agentMemberID string) (ActiveChannelAgentEntry, bool, error) {
	row := queryer.QueryRow(ctx, `
SELECT visibility, owner_member_id
FROM scope_memberships
WHERE scope_id = $1
  AND member_id = $2
  AND visibility IN ('public', 'private')
  AND removed_at IS NULL
ORDER BY CASE WHEN visibility = 'private' THEN 0 ELSE 1 END, effective_from_seq DESC
LIMIT 1
`, strings.TrimSpace(channelID), strings.TrimSpace(agentMemberID))

	var entry ActiveChannelAgentEntry
	if err := row.Scan(&entry.Visibility, &entry.OwnerMemberID); err != nil {
		if coredb.IsNotFound(err) {
			return ActiveChannelAgentEntry{}, false, nil
		}
		return ActiveChannelAgentEntry{}, false, fmt.Errorf("load active channel agent entry: %w", err)
	}
	return entry, true, nil
}

func (store *Store) ensurePublicChannelAgentEntryAllowed(ctx context.Context, queryer interface {
	QueryRow(context.Context, string, ...any) coredb.Row
}, channelID string, agentMemberID string) error {
	entry, ok, err := store.loadActiveChannelAgentEntry(ctx, queryer, channelID, agentMemberID)
	if err != nil || !ok {
		return err
	}
	if strings.TrimSpace(entry.Visibility) != channelAgentEntryVisibilityPublic {
		return conflict("agent already has an active private entry in this channel")
	}
	return nil
}

func (store *Store) ensurePrivateChannelAgentEntry(ctx context.Context, tx coredb.Tx, workspaceID string, channelID string, agent AgentEntryOwner, createdAt time.Time) error {
	if _, err := store.ensureChannelScopeProjection(ctx, tx, channelID); err != nil {
		return err
	}
	entry, ok, err := store.loadActiveChannelAgentEntry(ctx, tx, channelID, agent.AgentMemberID)
	if err != nil {
		return err
	}
	if ok {
		if strings.TrimSpace(entry.Visibility) != channelAgentEntryVisibilityPrivate {
			return conflict("agent already has an active public entry in this channel")
		}
		if strings.TrimSpace(entry.OwnerMemberID.String) != strings.TrimSpace(agent.OwnerMemberID) {
			return conflict("agent already has an active private entry for another owner")
		}
		return ensurePrivateAgentScopeMembership(ctx, tx, channelID, agent, createdAt)
	}

	if err := ensurePrivateAgentScopeMembership(ctx, tx, channelID, agent, createdAt); err != nil {
		return err
	}
	_ = workspaceID
	return nil
}

func ensurePrivateAgentScopeMembership(ctx context.Context, execer coredb.QueryExecer, channelID string, agent AgentEntryOwner, createdAt time.Time) error {
	if err := message.EnsureScopeMembership(ctx, execer, strings.TrimSpace(channelID), strings.TrimSpace(agent.AgentMemberID), channelAgentEntryVisibilityPrivate, strings.TrimSpace(agent.OwnerMemberID), "", 0, 0, createdAt.UTC()); err != nil {
		return fmt.Errorf("insert private channel agent scope membership: %w", err)
	}
	return nil
}

func markPrivateAgentScopeMembershipRemoved(ctx context.Context, execer coredb.QueryExecer, channelID string, agentMemberID string, ownerMemberID string, removedAt time.Time) error {
	trimmedChannelID := strings.TrimSpace(channelID)
	trimmedAgentMemberID := strings.TrimSpace(agentMemberID)
	trimmedOwnerMemberID := strings.TrimSpace(ownerMemberID)
	if trimmedChannelID == "" || trimmedAgentMemberID == "" || trimmedOwnerMemberID == "" {
		return nil
	}
	if _, err := execer.Exec(ctx, `
UPDATE scope_memberships
SET unread_generation = unread_generation + 1,
    removed_at = $4,
    updated_at = $4
WHERE scope_id IN (
    SELECT id
    FROM scopes
    WHERE id = $1 OR parent_scope_id = $1
  )
  AND member_id = $2
  AND owner_member_id = $3
  AND visibility = 'private'
  AND removed_at IS NULL
`, trimmedChannelID, trimmedAgentMemberID, trimmedOwnerMemberID, removedAt.UTC()); err != nil {
		return fmt.Errorf("remove private channel agent scope membership: %w", err)
	}
	return nil
}

func markOwnerPrivateScopeMembershipsRemovedForHostChannel(ctx context.Context, execer coredb.QueryExecer, hostChannelID string, ownerMemberID string, removedAt time.Time) error {
	trimmedHostChannelID := strings.TrimSpace(hostChannelID)
	trimmedOwnerMemberID := strings.TrimSpace(ownerMemberID)
	if trimmedHostChannelID == "" || trimmedOwnerMemberID == "" {
		return nil
	}
	if _, err := execer.Exec(ctx, `
UPDATE scope_memberships sm
SET sm.removed_at = $3,
    sm.updated_at = $3,
    sm.unread_generation = sm.unread_generation + 1
WHERE sm.scope_id IN (
    SELECT id
    FROM scopes
    WHERE id = $1 OR parent_scope_id = $1
  )
  AND sm.owner_member_id = $2
  AND sm.visibility = 'private'
  AND sm.removed_at IS NULL
`, trimmedHostChannelID, trimmedOwnerMemberID, removedAt.UTC()); err != nil {
		return fmt.Errorf("remove owner private channel agent scope memberships: %w", err)
	}
	return nil
}

func (store *Store) removePrivateEntriesForOwnerHostChannel(ctx context.Context, execer coredb.QueryExecer, ownerMemberID string, hostChannelID string, removedAt time.Time) error {
	now := removedAt.UTC()
	if now.IsZero() {
		now = time.Now().UTC()
	}
	if _, err := execer.Exec(ctx, `
DELETE sns
FROM scope_notification_settings sns
JOIN scope_memberships sm
  ON sm.scope_id = sns.scope_id
 AND sm.member_id = sns.member_id
JOIN scopes scope
  ON scope.id = sns.scope_id
WHERE sm.owner_member_id = $2
  AND sm.visibility = 'private'
  AND sm.removed_at IS NULL
  AND (scope.id = $1 OR scope.parent_scope_id = $1)
`, strings.TrimSpace(hostChannelID), strings.TrimSpace(ownerMemberID)); err != nil {
		return fmt.Errorf("delete owner private wake subscriptions for channel: %w", err)
	}
	if err := markOwnerPrivateScopeMembershipsRemovedForHostChannel(ctx, execer, hostChannelID, ownerMemberID, now); err != nil {
		return err
	}
	return nil
}

func (store *Store) removePrivateEntriesForOwner(ctx context.Context, execer coredb.QueryExecer, ownerMemberID string, removedAt time.Time) error {
	trimmedOwnerMemberID := strings.TrimSpace(ownerMemberID)
	if trimmedOwnerMemberID == "" {
		return nil
	}
	now := removedAt.UTC()
	if now.IsZero() {
		now = time.Now().UTC()
	}
	if _, err := execer.Exec(ctx, `
DELETE sns
FROM scope_notification_settings sns
JOIN scope_memberships sm
  ON sm.scope_id = sns.scope_id
 AND sm.member_id = sns.member_id
WHERE sm.owner_member_id = $1
  AND sm.visibility = 'private'
  AND sm.removed_at IS NULL
`, trimmedOwnerMemberID); err != nil {
		return fmt.Errorf("delete owner private wake subscriptions: %w", err)
	}
	if _, err := execer.Exec(ctx, `
UPDATE scope_memberships
SET unread_generation = unread_generation + 1,
    removed_at = $2,
    updated_at = $2
WHERE owner_member_id = $1
  AND visibility = 'private'
  AND removed_at IS NULL
`, trimmedOwnerMemberID, now); err != nil {
		return fmt.Errorf("remove owner private scope memberships: %w", err)
	}
	return nil
}
