package team

import (
	"context"
	"fmt"
	"strings"
	"time"
	"unicode"
	"unicode/utf8"

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

// CreateTeam creates a Team and its initial agent assignments.
func (store *Store) CreateTeam(ctx context.Context, params CreateTeamParams) (model.WorkspaceTeam, error) {
	txBeginner, err := txBeginnerFromStore(store)
	if err != nil {
		return model.WorkspaceTeam{}, err
	}
	var createdTeam model.WorkspaceTeam
	err = coredb.WithTx(ctx, txBeginner, func(tx coredb.Tx) error {
		team, err := store.InTx(tx).CreateTeam(ctx, params)
		if err != nil {
			return err
		}
		createdTeam = team
		return nil
	})
	if err != nil {
		return model.WorkspaceTeam{}, err
	}
	return createdTeam, nil
}

// CreateTeam creates a Team in the current transaction and inserts initial
// agent assignments.
func (store *TxStore) CreateTeam(ctx context.Context, params CreateTeamParams) (model.WorkspaceTeam, error) {
	execer, err := execerFromTxStore(store)
	if err != nil {
		return model.WorkspaceTeam{}, err
	}
	team, err := createTeam(ctx, execer, params)
	if err != nil {
		return model.WorkspaceTeam{}, err
	}
	seenMembers := make(map[string]struct{})
	if err := addInitialTeamMembers(ctx, execer, team.WorkspaceID, team.ID, team.CreatedByMemberID, params.Now, seenMembers, params.InitialMembers, 20); err != nil {
		return model.WorkspaceTeam{}, err
	}
	if err := requireTeamMemberInvariants(ctx, execer, team.WorkspaceID, team.ID); err != nil {
		return model.WorkspaceTeam{}, err
	}
	team, err = ensureTeamChannelForTeam(ctx, execer, team, params.Now)
	if err != nil {
		return model.WorkspaceTeam{}, err
	}
	return team, nil
}

func addInitialTeamMembers(
	ctx context.Context,
	execer coredb.QueryExecer,
	workspaceID string,
	teamID string,
	addedByMemberID string,
	now time.Time,
	seenMembers map[string]struct{},
	members []AddTeamMemberParams,
	startSortOrder int64,
) error {
	effectiveMembers := make([]AddTeamMemberParams, 0, len(members))
	memberIndexes := make([]int, 0, len(members))
	for index, member := range members {
		if trim(member.ActorID) == "" {
			continue
		}
		memberKey := activeMemberKey(teamID, member.ActorKind, member.ActorID, false).(string)
		if _, ok := seenMembers[memberKey]; ok {
			continue
		}
		seenMembers[memberKey] = struct{}{}
		effectiveMembers = append(effectiveMembers, member)
		memberIndexes = append(memberIndexes, index)
	}
	hasLeader := false
	for _, member := range effectiveMembers {
		if trim(member.Role) == MemberRoleLeader {
			hasLeader = true
			break
		}
	}
	for effectiveIndex, member := range effectiveMembers {
		if !hasLeader && effectiveIndex == 0 {
			member.Role = MemberRoleLeader
		}
		member.WorkspaceID = workspaceID
		member.TeamID = teamID
		member.AddedByMemberID = addedByMemberID
		member.Now = now
		if member.SortOrder == 0 {
			member.SortOrder = startSortOrder + int64(memberIndexes[effectiveIndex]*10)
		}
		if _, err := addTeamMember(ctx, execer, member); err != nil {
			return err
		}
	}
	return nil
}

// LoadTeam loads one Team by id.
func (store *Store) LoadTeam(ctx context.Context, workspaceID string, teamID string) (model.WorkspaceTeam, error) {
	queryer, err := queryerFromStore(store)
	if err != nil {
		return model.WorkspaceTeam{}, err
	}
	return loadTeam(ctx, queryer, workspaceID, teamID)
}

// LoadTeam loads one Team by id in the current transaction.
func (store *TxStore) LoadTeam(ctx context.Context, workspaceID string, teamID string) (model.WorkspaceTeam, error) {
	queryer, err := queryerFromTxStore(store)
	if err != nil {
		return model.WorkspaceTeam{}, err
	}
	return loadTeam(ctx, queryer, workspaceID, teamID)
}

// LoadTeamByHandle loads one active Team by handle.
func (store *Store) LoadTeamByHandle(ctx context.Context, workspaceID string, handle string) (model.WorkspaceTeam, error) {
	queryer, err := queryerFromStore(store)
	if err != nil {
		return model.WorkspaceTeam{}, err
	}
	return loadTeamByHandle(ctx, queryer, workspaceID, handle)
}

// UpdateTeam updates editable Team profile fields.
func (store *Store) UpdateTeam(ctx context.Context, workspaceID string, teamID string, params UpdateTeamParams) (model.WorkspaceTeam, error) {
	execer, err := execerFromStore(store)
	if err != nil {
		return model.WorkspaceTeam{}, err
	}
	return updateTeam(ctx, execer, workspaceID, teamID, params)
}

// DeleteTeam permanently deletes one Team and cascades its agent assignments.
func (store *Store) DeleteTeam(ctx context.Context, workspaceID string, teamID string) error {
	workspaceID = trim(workspaceID)
	teamID = trim(teamID)
	if workspaceID == "" || teamID == "" {
		return badRequest("team workspaceId and teamId are required")
	}
	txBeginner, err := txBeginnerFromStore(store)
	if err != nil {
		return err
	}
	return coredb.WithTx(ctx, txBeginner, func(tx coredb.Tx) error {
		return store.InTx(tx).DeleteTeam(ctx, workspaceID, teamID)
	})
}

// DeleteTeam permanently deletes one Team in the current transaction.
func (store *TxStore) DeleteTeam(ctx context.Context, workspaceID string, teamID string) error {
	execer, err := execerFromTxStore(store)
	if err != nil {
		return err
	}
	return deleteTeam(ctx, execer, workspaceID, teamID)
}

func createTeam(ctx context.Context, execer coredb.QueryExecer, params CreateTeamParams) (model.WorkspaceTeam, error) {
	workspaceID := trim(params.WorkspaceID)
	handle, err := normalizeHandle(params.Handle)
	if err != nil {
		return model.WorkspaceTeam{}, err
	}
	displayName, err := normalizeRequiredTeamString(params.DisplayName, MaxTeamDisplayNameChars, "team displayName")
	if err != nil {
		return model.WorkspaceTeam{}, err
	}
	description, err := normalizeOptionalTeamString(params.Description, MaxTeamDescriptionChars, "team description")
	if err != nil {
		return model.WorkspaceTeam{}, err
	}
	avatarURL, err := normalizeOptionalTeamString(params.AvatarURL, MaxTeamAvatarURLChars, "team avatarUrl")
	if err != nil {
		return model.WorkspaceTeam{}, err
	}
	createdBy := trim(params.CreatedByMemberID)
	if workspaceID == "" || createdBy == "" {
		return model.WorkspaceTeam{}, badRequest("team workspaceId, displayName, and createdByMemberId are required")
	}
	if err := requireActiveWorkspaceMember(ctx, execer, workspaceID, createdBy, ActorKindHuman); err != nil {
		return model.WorkspaceTeam{}, err
	}
	visibility, err := normalizeVisibility(params.Visibility)
	if err != nil {
		return model.WorkspaceTeam{}, err
	}
	now := params.Now.UTC()
	if now.IsZero() {
		now = time.Now().UTC()
	}
	teamID := trim(params.ID)
	if teamID == "" {
		generatedID, err := identifiers.NewEntityID("team")
		if err != nil {
			return model.WorkspaceTeam{}, fmt.Errorf("generate team id: %w", err)
		}
		teamID = generatedID
	}
	if _, err := execer.Exec(ctx, `
INSERT INTO workspace_teams (
    id, workspace_id, handle, display_name, description, avatar_url,
    visibility, status, created_by_member_id, created_at, updated_at, archived_at, active_handle_key
) VALUES (
    $1, $2, $3, $4, $5, $6,
    $7, $8, $9, $10, $10, NULL, $11
)
`, teamID, workspaceID, handle, displayName, description, avatarURL, visibility, StatusActive, createdBy, now, activeHandleKey(workspaceID, handle, StatusActive)); err != nil {
		return model.WorkspaceTeam{}, fmt.Errorf("create team: %w", mapTeamWriteError(err))
	}
	return loadTeam(ctx, execer, workspaceID, teamID)
}

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

func loadTeamByHandle(ctx context.Context, queryer coredb.QueryRower, workspaceID string, handle string) (model.WorkspaceTeam, error) {
	normalized, err := normalizeHandle(handle)
	if err != nil {
		return model.WorkspaceTeam{}, err
	}
	row := queryer.QueryRow(ctx, teamSelectSQL()+` WHERE active_handle_key = $1`, activeHandleKey(workspaceID, normalized, StatusActive))
	team, err := scanTeam(row)
	if err != nil {
		if coredb.IsNotFound(err) {
			return model.WorkspaceTeam{}, notFound("team not found")
		}
		return model.WorkspaceTeam{}, fmt.Errorf("load team by handle: %w", err)
	}
	return team, nil
}

func updateTeam(ctx context.Context, execer coredb.QueryExecer, workspaceID string, teamID string, params UpdateTeamParams) (model.WorkspaceTeam, error) {
	current, err := loadTeam(ctx, execer, workspaceID, teamID)
	if err != nil {
		return model.WorkspaceTeam{}, err
	}
	handle := current.Handle
	if params.Handle != nil {
		handle, err = normalizeHandle(*params.Handle)
		if err != nil {
			return model.WorkspaceTeam{}, err
		}
	}
	displayName := current.DisplayName
	if params.DisplayName != nil {
		displayName, err = normalizeRequiredTeamString(*params.DisplayName, MaxTeamDisplayNameChars, "team displayName")
		if err != nil {
			return model.WorkspaceTeam{}, err
		}
	}
	description := current.Description
	if params.Description != nil {
		description, err = normalizeOptionalTeamString(*params.Description, MaxTeamDescriptionChars, "team description")
		if err != nil {
			return model.WorkspaceTeam{}, err
		}
	}
	avatarURL := current.AvatarURL
	if params.AvatarURL != nil {
		avatarURL, err = normalizeOptionalTeamString(*params.AvatarURL, MaxTeamAvatarURLChars, "team avatarUrl")
		if err != nil {
			return model.WorkspaceTeam{}, err
		}
	}
	visibility := current.Visibility
	if params.Visibility != nil {
		visibility, err = normalizeVisibility(*params.Visibility)
		if err != nil {
			return model.WorkspaceTeam{}, err
		}
	}
	now := params.Now.UTC()
	if now.IsZero() {
		now = time.Now().UTC()
	}
	if _, err := execer.Exec(ctx, `
UPDATE workspace_teams
SET handle = $3,
    display_name = $4,
    description = $5,
    avatar_url = $6,
    visibility = $7,
    updated_at = $8,
    active_handle_key = $9
WHERE workspace_id = $1 AND id = $2
`, trim(workspaceID), trim(teamID), handle, displayName, description, avatarURL, visibility, now, activeHandleKey(workspaceID, handle, current.Status)); err != nil {
		return model.WorkspaceTeam{}, fmt.Errorf("update team: %w", mapTeamWriteError(err))
	}
	updated, err := loadTeam(ctx, execer, workspaceID, teamID)
	if err != nil {
		return model.WorkspaceTeam{}, err
	}
	if updated.Status == StatusActive {
		return ensureTeamChannelForTeam(ctx, execer, updated, now)
	}
	return updated, nil
}

func deleteTeam(ctx context.Context, execer coredb.QueryExecer, workspaceID string, teamID string) error {
	workspaceID = trim(workspaceID)
	teamID = trim(teamID)
	if workspaceID == "" || teamID == "" {
		return badRequest("team workspaceId and teamId are required")
	}
	team, err := loadTeam(ctx, execer, workspaceID, teamID)
	if err != nil {
		return err
	}
	if err := archiveTeamChannelProjection(ctx, execer, team.ChannelID, time.Now().UTC()); err != nil {
		return err
	}
	result, err := execer.Exec(ctx, `
DELETE FROM workspace_teams
WHERE workspace_id = $1 AND id = $2
`, workspaceID, teamID)
	if err != nil {
		return fmt.Errorf("delete team: %w", mapTeamWriteError(err))
	}
	if result.RowsAffected() == 0 {
		return notFound("team not found")
	}
	return nil
}

const (
	MaxTeamHandleChars        = 64
	MaxTeamDisplayNameChars   = 128
	MaxTeamDescriptionChars   = 32768
	MaxTeamAvatarURLChars     = 2048
	MaxTeamRoleLabelChars     = 128
	MaxRoleOverlayPromptChars = 8192
)

func normalizeHandle(value string) (string, error) {
	handle := strings.ToLower(trim(value))
	if handle == "" {
		return "", badRequest("team handle is required")
	}
	if utf8.RuneCountInString(handle) > MaxTeamHandleChars {
		return "", badRequest("team handle must be at most 64 characters")
	}
	for index, r := range handle {
		allowedNameRune := unicode.IsLetter(r) || unicode.IsNumber(r)
		allowedSeparator := r == '-' || r == '_'
		if !allowedNameRune && !allowedSeparator {
			return "", badRequest("team handle must contain only letters, numbers, Chinese characters, hyphen, or underscore")
		}
		if index == 0 && !allowedNameRune {
			return "", badRequest("team handle must start with a letter, number, or Chinese character")
		}
	}
	return handle, nil
}

func normalizeRequiredTeamString(value string, maxChars int, fieldName string) (string, error) {
	normalized, err := normalizeOptionalTeamString(value, maxChars, fieldName)
	if err != nil {
		return "", err
	}
	if normalized == "" {
		return "", badRequest(fieldName + " is required")
	}
	return normalized, nil
}

func normalizeOptionalTeamString(value string, maxChars int, fieldName string) (string, error) {
	normalized := strings.TrimSpace(value)
	if maxChars >= 0 && utf8.RuneCountInString(normalized) > maxChars {
		return "", badRequest(fmt.Sprintf("%s must be at most %d characters", fieldName, maxChars))
	}
	return normalized, nil
}
