package team

import (
	"context"
	"fmt"
	"time"

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

// AddTeamMember adds one active Team member.
func (store *Store) AddTeamMember(ctx context.Context, params AddTeamMemberParams) (model.WorkspaceTeamMember, error) {
	txBeginner, err := txBeginnerFromStore(store)
	if err != nil {
		return model.WorkspaceTeamMember{}, err
	}
	var member model.WorkspaceTeamMember
	add := func() error {
		return coredb.WithTx(ctx, txBeginner, func(tx coredb.Tx) error {
			added, err := addTeamMember(ctx, tx, params)
			if err != nil {
				return err
			}
			if err := requireTeamMemberInvariants(ctx, tx, added.WorkspaceID, added.TeamID); err != nil {
				return err
			}
			team, err := loadTeam(ctx, tx, added.WorkspaceID, added.TeamID)
			if err != nil {
				return err
			}
			if _, err := ensureTeamChannelForTeam(ctx, tx, team, params.Now); err != nil {
				return err
			}
			member = added
			return nil
		})
	}
	err = add()
	if err != nil {
		return model.WorkspaceTeamMember{}, err
	}
	return member, nil
}

// AddTeamMember adds one active Team member inside a transaction.
func (store *TxStore) AddTeamMember(ctx context.Context, params AddTeamMemberParams) (model.WorkspaceTeamMember, error) {
	execer, err := execerFromTxStore(store)
	if err != nil {
		return model.WorkspaceTeamMember{}, err
	}
	member, err := addTeamMember(ctx, execer, params)
	if err != nil {
		return model.WorkspaceTeamMember{}, err
	}
	if err := requireTeamMemberInvariants(ctx, execer, member.WorkspaceID, member.TeamID); err != nil {
		return model.WorkspaceTeamMember{}, err
	}
	team, err := loadTeam(ctx, execer, member.WorkspaceID, member.TeamID)
	if err != nil {
		return model.WorkspaceTeamMember{}, err
	}
	if _, err := ensureTeamChannelForTeam(ctx, execer, team, params.Now); err != nil {
		return model.WorkspaceTeamMember{}, err
	}
	return member, nil
}

// LoadTeamMember loads one Team member by row id.
func (store *Store) LoadTeamMember(ctx context.Context, workspaceID string, teamID string, teamMemberID string) (model.WorkspaceTeamMember, error) {
	queryer, err := queryerFromStore(store)
	if err != nil {
		return model.WorkspaceTeamMember{}, err
	}
	return loadTeamMember(ctx, queryer, workspaceID, teamID, teamMemberID)
}

// ListActiveTeamMembers lists active members for one Team in sort order.
func (store *Store) ListActiveTeamMembers(ctx context.Context, workspaceID string, teamID string) ([]model.WorkspaceTeamMember, error) {
	queryer, err := queryerFromStore(store)
	if err != nil {
		return nil, err
	}
	return listActiveTeamMembers(ctx, queryer, workspaceID, teamID)
}

// UpdateTeamMember updates one agent assignment.
func (store *Store) UpdateTeamMember(ctx context.Context, workspaceID string, teamID string, teamMemberID string, params UpdateTeamMemberParams) (model.WorkspaceTeamMember, error) {
	txBeginner, err := txBeginnerFromStore(store)
	if err != nil {
		return model.WorkspaceTeamMember{}, err
	}
	var updated model.WorkspaceTeamMember
	err = coredb.WithTx(ctx, txBeginner, func(tx coredb.Tx) error {
		member, err := updateTeamMember(ctx, tx, workspaceID, teamID, teamMemberID, params)
		if err != nil {
			return err
		}
		if err := requireTeamMemberInvariants(ctx, tx, member.WorkspaceID, member.TeamID); err != nil {
			return err
		}
		updated = member
		return nil
	})
	if err != nil {
		return model.WorkspaceTeamMember{}, err
	}
	return updated, nil
}

// RemoveTeamMember soft-removes one active agent assignment.
func (store *Store) RemoveTeamMember(ctx context.Context, workspaceID string, teamID string, teamMemberID string, removedAt time.Time) error {
	txBeginner, err := txBeginnerFromStore(store)
	if err != nil {
		return err
	}
	return coredb.WithTx(ctx, txBeginner, func(tx coredb.Tx) error {
		current, err := loadTeamMember(ctx, tx, workspaceID, teamID, teamMemberID)
		if err != nil {
			return err
		}
		if err := removeTeamMember(ctx, tx, workspaceID, teamID, teamMemberID, removedAt); err != nil {
			return err
		}
		if err := requireTeamMemberInvariants(ctx, tx, workspaceID, teamID); err != nil {
			return err
		}
		team, err := loadTeam(ctx, tx, workspaceID, teamID)
		if err != nil {
			return err
		}
		return removeTeamChannelMember(ctx, tx, team, current, removedAt)
	})
}

// RemoveActiveAgentMemberships soft-removes every active Team assignment for one agent.
func (store *TxStore) RemoveActiveAgentMemberships(ctx context.Context, workspaceID string, agentMemberID string, removedAt time.Time) error {
	execer, err := execerFromTxStore(store)
	if err != nil {
		return err
	}
	now := removedAt.UTC()
	if now.IsZero() {
		now = time.Now().UTC()
	}
	if _, err := execer.Exec(ctx, `
UPDATE workspace_team_members
SET removed_at = $3,
    updated_at = $3,
    active_member_key = NULL,
    active_agent_team_key = NULL,
    active_leader_key = NULL
WHERE workspace_id = $1
  AND actor_kind = 'agent'
  AND actor_id = $2
  AND removed_at IS NULL
`, trim(workspaceID), trim(agentMemberID), now); err != nil {
		return fmt.Errorf("remove active team memberships for agent member %s in workspace %s: %w", trim(agentMemberID), trim(workspaceID), mapTeamWriteError(err))
	}
	return nil
}

func addTeamMember(ctx context.Context, execer coredb.QueryExecer, params AddTeamMemberParams) (model.WorkspaceTeamMember, error) {
	workspaceID := trim(params.WorkspaceID)
	teamID := trim(params.TeamID)
	actorKind, err := normalizeActorKind(params.ActorKind)
	if err != nil {
		return model.WorkspaceTeamMember{}, err
	}
	actorID := trim(params.ActorID)
	addedBy := trim(params.AddedByMemberID)
	if workspaceID == "" || teamID == "" || actorID == "" || addedBy == "" {
		return model.WorkspaceTeamMember{}, badRequest("team member workspaceId, teamId, actorId, and addedByMemberId are required")
	}
	if _, err := loadActiveTeam(ctx, execer, workspaceID, teamID); err != nil {
		return model.WorkspaceTeamMember{}, err
	}
	if err := requireActiveWorkspaceMember(ctx, execer, workspaceID, addedBy, ""); err != nil {
		return model.WorkspaceTeamMember{}, err
	}
	if err := validateTeamActor(ctx, execer, workspaceID, teamID, actorKind, actorID); err != nil {
		return model.WorkspaceTeamMember{}, err
	}
	role, err := normalizeMemberRole(params.Role)
	if err != nil {
		return model.WorkspaceTeamMember{}, err
	}
	if role == "" {
		role = MemberRoleMember
	}
	roleLabel, err := normalizeOptionalTeamString(params.RoleLabel, MaxTeamRoleLabelChars, "team member roleLabel")
	if err != nil {
		return model.WorkspaceTeamMember{}, err
	}
	roleOverlayPrompt, err := normalizeRoleOverlayPromptForActorKind(actorKind, params.RoleOverlayPrompt)
	if err != nil {
		return model.WorkspaceTeamMember{}, err
	}
	now := params.Now.UTC()
	if now.IsZero() {
		now = time.Now().UTC()
	}
	teamMemberID := trim(params.ID)
	if teamMemberID == "" {
		generatedID, err := identifiers.NewEntityID("wtm")
		if err != nil {
			return model.WorkspaceTeamMember{}, fmt.Errorf("generate team member id: %w", err)
		}
		teamMemberID = generatedID
	}
	wakeOnTeamMention := role == MemberRoleLeader
	if params.WakeOnTeamMention != nil {
		wakeOnTeamMention = *params.WakeOnTeamMention
	}
	sortOrder := params.SortOrder
	if sortOrder == 0 {
		sortOrder, err = nextTeamMemberSortOrder(ctx, execer, workspaceID, teamID)
		if err != nil {
			return model.WorkspaceTeamMember{}, err
		}
	}
	if role == MemberRoleLeader {
		if err := demoteActiveTeamLeaders(ctx, execer, workspaceID, teamID, teamMemberID, now); err != nil {
			return model.WorkspaceTeamMember{}, err
		}
	}
	if _, err := execer.Exec(ctx, `
INSERT INTO workspace_team_members (
    id, workspace_id, team_id, actor_kind, actor_id,
    role, role_label, role_overlay_prompt, role_overlay_version,
    wake_on_team_mention, sort_order, added_by_member_id, added_at, updated_at, removed_at, active_member_key,
    active_agent_team_key, active_leader_key
) VALUES (
    $1, $2, $3, $4, $5,
    $6, $7, $8, 1,
    $9, $10, $11, $12, $12, NULL, $13,
    $14, $15
)
`, teamMemberID, workspaceID, teamID, actorKind, actorID, role, roleLabel, roleOverlayPrompt, wakeOnTeamMention, sortOrder, addedBy, now, activeMemberKey(teamID, actorKind, actorID, false), activeAgentTeamKey(workspaceID, actorKind, actorID, false), activeLeaderKey(workspaceID, teamID, actorKind, role, false)); err != nil {
		return model.WorkspaceTeamMember{}, fmt.Errorf("add team member: %w", mapTeamWriteError(err))
	}
	return loadTeamMember(ctx, execer, workspaceID, teamID, teamMemberID)
}

func nextTeamMemberSortOrder(ctx context.Context, queryer coredb.QueryRower, workspaceID string, teamID string) (int64, error) {
	var sortOrder int64
	if err := queryer.QueryRow(ctx, `
SELECT COALESCE(MAX(sort_order), 0) + 10
FROM workspace_team_members
WHERE workspace_id = $1
  AND team_id = $2
  AND removed_at IS NULL
`, trim(workspaceID), trim(teamID)).Scan(&sortOrder); err != nil {
		return 0, fmt.Errorf("load next team member sort order: %w", err)
	}
	return sortOrder, nil
}

func loadActiveTeam(ctx context.Context, queryer coredb.QueryRower, workspaceID string, teamID string) (model.WorkspaceTeam, error) {
	team, err := loadTeam(ctx, queryer, workspaceID, teamID)
	if err != nil {
		return model.WorkspaceTeam{}, err
	}
	if team.Status != StatusActive {
		return model.WorkspaceTeam{}, conflict("team is archived")
	}
	return team, nil
}

func loadTeamMember(ctx context.Context, queryer coredb.QueryRower, workspaceID string, teamID string, teamMemberID string) (model.WorkspaceTeamMember, error) {
	row := queryer.QueryRow(ctx, teamMemberSelectSQL()+` WHERE workspace_id = $1 AND team_id = $2 AND id = $3`, trim(workspaceID), trim(teamID), trim(teamMemberID))
	member, err := scanTeamMember(row)
	if err != nil {
		if coredb.IsNotFound(err) {
			return model.WorkspaceTeamMember{}, notFound("team member not found")
		}
		return model.WorkspaceTeamMember{}, fmt.Errorf("load team member: %w", err)
	}
	return member, nil
}

func listActiveTeamMembers(ctx context.Context, queryer coredb.Queryer, workspaceID string, teamID string) ([]model.WorkspaceTeamMember, error) {
	rows, err := queryer.Query(ctx, teamMemberSelectSQL()+` WHERE workspace_id = $1 AND team_id = $2 AND removed_at IS NULL ORDER BY sort_order, added_at, id`, trim(workspaceID), trim(teamID))
	if err != nil {
		return nil, fmt.Errorf("list team members: %w", err)
	}
	defer rows.Close()
	members := make([]model.WorkspaceTeamMember, 0)
	for rows.Next() {
		member, err := scanTeamMember(rows)
		if err != nil {
			return nil, fmt.Errorf("scan team member: %w", err)
		}
		members = append(members, member)
	}
	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("iterate team members: %w", err)
	}
	return members, nil
}

func requireTeamMemberInvariants(ctx context.Context, queryer coredb.QueryRower, workspaceID string, teamID string) error {
	var memberCount int64
	var leaderCount int64
	var automaticResponderCount int64
	if err := queryer.QueryRow(ctx, `
	SELECT COUNT(*),
	       COALESCE(SUM(CASE WHEN actor_kind = 'agent' AND role = 'leader' THEN 1 ELSE 0 END), 0),
	       COALESCE(SUM(CASE WHEN actor_kind = 'agent' AND wake_on_team_mention THEN 1 ELSE 0 END), 0)
	FROM workspace_team_members
	WHERE workspace_id = $1
	  AND team_id = $2
	  AND removed_at IS NULL
	`, trim(workspaceID), trim(teamID)).Scan(&memberCount, &leaderCount, &automaticResponderCount); err != nil {
		return fmt.Errorf("count team member invariants: %w", err)
	}
	if memberCount == 0 {
		return badRequest("team must have at least one member")
	}
	if leaderCount != 1 {
		return badRequest("team must have one leader")
	}
	if automaticResponderCount == 0 {
		return badRequest("team must have at least one automatic responder")
	}
	return nil
}

func updateTeamMember(ctx context.Context, execer coredb.QueryExecer, workspaceID string, teamID string, teamMemberID string, params UpdateTeamMemberParams) (model.WorkspaceTeamMember, error) {
	current, err := loadTeamMember(ctx, execer, workspaceID, teamID, teamMemberID)
	if err != nil {
		return model.WorkspaceTeamMember{}, err
	}
	if current.RemovedAt != nil {
		return model.WorkspaceTeamMember{}, conflict("team member is removed")
	}
	role := current.Role
	if role == "" {
		role = MemberRoleMember
	}
	if params.Role != nil {
		role, err = normalizeMemberRole(*params.Role)
		if err != nil {
			return model.WorkspaceTeamMember{}, err
		}
		if role == "" {
			role = MemberRoleMember
		}
	}
	roleLabel := current.RoleLabel
	if params.RoleLabel != nil {
		roleLabel, err = normalizeOptionalTeamString(*params.RoleLabel, MaxTeamRoleLabelChars, "team member roleLabel")
		if err != nil {
			return model.WorkspaceTeamMember{}, err
		}
	}
	overlay := current.RoleOverlayPrompt
	overlayVersion := current.RoleOverlayVersion
	if params.RoleOverlayPrompt != nil {
		updatedOverlay, err := normalizeRoleOverlayPromptForActorKind(current.ActorKind, *params.RoleOverlayPrompt)
		if err != nil {
			return model.WorkspaceTeamMember{}, err
		}
		if updatedOverlay != overlay {
			overlay = updatedOverlay
			overlayVersion++
		}
	}
	sortOrder := current.SortOrder
	if params.SortOrder != nil {
		sortOrder = *params.SortOrder
	}
	wakeOnTeamMention := current.WakeOnTeamMention
	if params.WakeOnTeamMention != nil {
		wakeOnTeamMention = *params.WakeOnTeamMention
	}
	now := params.Now.UTC()
	if now.IsZero() {
		now = time.Now().UTC()
	}
	if role == MemberRoleLeader {
		if err := demoteActiveTeamLeaders(ctx, execer, trim(workspaceID), trim(teamID), trim(teamMemberID), now); err != nil {
			return model.WorkspaceTeamMember{}, err
		}
	} else if current.Role == MemberRoleLeader {
		return model.WorkspaceTeamMember{}, badRequest("team must have one leader; promote another member instead")
	}
	if _, err := execer.Exec(ctx, `
UPDATE workspace_team_members
SET role = $4,
    role_label = $5,
    role_overlay_prompt = $6,
    role_overlay_version = $7,
    wake_on_team_mention = $8,
    sort_order = $9,
    updated_at = $10,
    active_leader_key = $11
WHERE workspace_id = $1 AND team_id = $2 AND id = $3 AND removed_at IS NULL
`, trim(workspaceID), trim(teamID), trim(teamMemberID), role, roleLabel, overlay, overlayVersion, wakeOnTeamMention, sortOrder, now, activeLeaderKey(trim(workspaceID), trim(teamID), current.ActorKind, role, false)); err != nil {
		return model.WorkspaceTeamMember{}, fmt.Errorf("update team member: %w", mapTeamWriteError(err))
	}
	return loadTeamMember(ctx, execer, workspaceID, teamID, teamMemberID)
}

func normalizeRoleOverlayPromptForActorKind(actorKind string, value string) (string, error) {
	overlay, err := normalizeOptionalTeamString(value, MaxRoleOverlayPromptChars, "team member roleOverlayPrompt")
	if err != nil {
		return "", err
	}
	if actorKind == ActorKindAgent {
		return overlay, nil
	}
	if overlay != "" {
		return "", badRequest("team member roleOverlayPrompt is only supported for agent members")
	}
	return "", nil
}

func removeTeamMember(ctx context.Context, execer coredb.QueryExecer, workspaceID string, teamID string, teamMemberID string, removedAt time.Time) error {
	current, err := loadTeamMember(ctx, execer, workspaceID, teamID, teamMemberID)
	if err != nil {
		return err
	}
	if current.RemovedAt != nil {
		return nil
	}
	now := removedAt.UTC()
	if now.IsZero() {
		now = time.Now().UTC()
	}
	if _, err := execer.Exec(ctx, `
UPDATE workspace_team_members
SET removed_at = $4,
    updated_at = $4,
    active_member_key = NULL,
    active_agent_team_key = NULL,
    active_leader_key = NULL
WHERE workspace_id = $1 AND team_id = $2 AND id = $3 AND removed_at IS NULL
`, trim(workspaceID), trim(teamID), trim(teamMemberID), now); err != nil {
		return fmt.Errorf("remove team member: %w", mapTeamWriteError(err))
	}
	return nil
}

func normalizeMemberRole(value string) (string, error) {
	role := trim(value)
	if role == "" {
		return "", nil
	}
	switch role {
	case MemberRoleLeader, MemberRoleMember:
		return role, nil
	default:
		return "", badRequest("team member role must be leader or member")
	}
}

func demoteActiveTeamLeaders(ctx context.Context, execer coredb.QueryExecer, workspaceID string, teamID string, exceptMemberID string, now time.Time) error {
	if _, err := execer.Exec(ctx, `
UPDATE workspace_team_members
SET role = 'member',
    active_leader_key = NULL,
    updated_at = $4
WHERE workspace_id = $1
  AND team_id = $2
  AND id <> $3
  AND actor_kind = 'agent'
  AND role = 'leader'
  AND removed_at IS NULL
`, trim(workspaceID), trim(teamID), trim(exceptMemberID), now); err != nil {
		return fmt.Errorf("demote team leader: %w", mapTeamWriteError(err))
	}
	return nil
}
