package team

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

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

const (
	teamChannelAgentNotificationState = "auto-wake"
	teamChannelHumanNotificationState = "active"
)

func (store *Store) ensureTeamChannelView(ctx context.Context, viewer teamViewer, workspaceID string, teamID string) (TeamView, error) {
	txBeginner, err := txBeginnerFromStore(store)
	if err != nil {
		return TeamView{}, err
	}
	if err := coredb.WithTx(ctx, txBeginner, func(tx coredb.Tx) error {
		team, err := loadTeam(ctx, tx, workspaceID, teamID)
		if err != nil {
			return err
		}
		_, err = ensureTeamChannelForTeam(ctx, tx, team, time.Now().UTC())
		return err
	}); err != nil {
		return TeamView{}, err
	}
	queryer, err := queryerFromStore(store)
	if err != nil {
		return TeamView{}, err
	}
	return loadTeamViewForViewer(ctx, queryer, viewer, workspaceID, teamID)
}

// EnsureTeamChannel explicitly repairs the Team-owned channel and member
// projections for an active Team. It is reserved for mutation and repair
// workflows; routine reads must not call it.
func (store *Store) EnsureTeamChannel(ctx context.Context, workspaceID string, teamID string) (model.WorkspaceTeam, error) {
	txBeginner, err := txBeginnerFromStore(store)
	if err != nil {
		return model.WorkspaceTeam{}, err
	}
	var ensured model.WorkspaceTeam
	if err := coredb.WithTx(ctx, txBeginner, func(tx coredb.Tx) error {
		team, err := loadTeam(ctx, tx, workspaceID, teamID)
		if err != nil {
			return err
		}
		ensured, err = ensureTeamChannelForTeam(ctx, tx, team, time.Now().UTC())
		return err
	}); err != nil {
		return model.WorkspaceTeam{}, err
	}
	return ensured, nil
}

func ensureTeamChannelForTeam(ctx context.Context, execer coredb.QueryExecer, team model.WorkspaceTeam, now time.Time) (model.WorkspaceTeam, error) {
	team.ID = trim(team.ID)
	team.WorkspaceID = trim(team.WorkspaceID)
	team.CreatedByMemberID = trim(team.CreatedByMemberID)
	if team.ID == "" || team.WorkspaceID == "" {
		return model.WorkspaceTeam{}, badRequest("team workspaceId and teamId are required")
	}
	if team.Status != StatusActive {
		return team, nil
	}
	if now.IsZero() {
		now = time.Now().UTC()
	} else {
		now = now.UTC()
	}
	channelID := trim(team.ChannelID)
	if channelID == "" {
		generatedID, err := identifiers.NewEntityID("chn")
		if err != nil {
			return model.WorkspaceTeam{}, fmt.Errorf("generate team channel id: %w", err)
		}
		result, err := execer.Exec(ctx, `
UPDATE workspace_teams
SET channel_id = $3,
    updated_at = $4
WHERE workspace_id = $1
  AND id = $2
  AND (channel_id IS NULL OR channel_id = '')
`, team.WorkspaceID, team.ID, generatedID, now)
		if err != nil {
			return model.WorkspaceTeam{}, fmt.Errorf("bind team channel: %w", mapTeamWriteError(err))
		}
		if result.RowsAffected() > 0 {
			channelID = generatedID
			team.ChannelID = channelID
		} else {
			current, err := loadTeam(ctx, execer, team.WorkspaceID, team.ID)
			if err != nil {
				return model.WorkspaceTeam{}, err
			}
			team = current
			channelID = trim(team.ChannelID)
		}
	}
	if channelID == "" {
		return model.WorkspaceTeam{}, conflict("team channel could not be resolved")
	}
	team.ChannelID = channelID

	if err := upsertTeamChannelProjection(ctx, execer, team, now); err != nil {
		return model.WorkspaceTeam{}, err
	}
	if err := syncTeamChannelMemberships(ctx, execer, team, now); err != nil {
		return model.WorkspaceTeam{}, err
	}
	return team, nil
}

func upsertTeamChannelProjection(ctx context.Context, execer coredb.QueryExecer, team model.WorkspaceTeam, now time.Time) error {
	repos := message.NewScopeRepositories(execer)
	channelID := trim(team.ChannelID)
	if err := repos.Scopes.UpsertScope(ctx, message.ScopeRecord{
		ID:                channelID,
		WorkspaceID:       team.WorkspaceID,
		Type:              string(domain.ThreadTypeChannel),
		IdentityKey:       "agent-team-channel:" + team.ID,
		Status:            "active",
		CreatedByMemberID: team.CreatedByMemberID,
		CreatedAt:         now,
		UpdatedAt:         now,
	}); err != nil {
		return fmt.Errorf("upsert team channel scope: %w", err)
	}
	if err := repos.Scopes.UpsertChannelProfile(ctx, message.ScopeChannelProfileRecord{
		ScopeID:       channelID,
		WorkspaceID:   team.WorkspaceID,
		Slug:          team.ID,
		DisplayName:   strings.TrimSpace(team.DisplayName),
		Description:   strings.TrimSpace(team.Description),
		Visibility:    "private",
		Subsystem:     message.ChannelSubsystemTeam,
		OwnerMemberID: team.CreatedByMemberID,
		IsAllChannel:  false,
		CreatedAt:     now,
		UpdatedAt:     now,
	}); err != nil {
		return fmt.Errorf("upsert team channel profile: %w", err)
	}
	return nil
}

func syncTeamChannelMemberships(ctx context.Context, execer coredb.QueryExecer, team model.WorkspaceTeam, now time.Time) error {
	if trim(team.CreatedByMemberID) != "" {
		if err := ensureTeamChannelMember(ctx, execer, team.ChannelID, team.CreatedByMemberID, teamChannelHumanNotificationState, now); err != nil {
			return err
		}
	}
	members, err := listActiveTeamMembers(ctx, execer, team.WorkspaceID, team.ID)
	if err != nil {
		return err
	}
	for _, member := range members {
		if member.ActorKind != ActorKindAgent || trim(member.ActorID) == "" {
			continue
		}
		if err := ensureTeamChannelMember(ctx, execer, team.ChannelID, member.ActorID, teamChannelAgentNotificationState, now); err != nil {
			return err
		}
	}
	return nil
}

func ensureTeamChannelMember(ctx context.Context, execer coredb.QueryExecer, channelID string, memberID string, notificationState string, now time.Time) error {
	var latestSeq, latestGlobalSeq int64
	if err := execer.QueryRow(ctx, `
SELECT latest_seq, latest_global_seq
FROM scopes
WHERE id = $1
`, trim(channelID)).Scan(&latestSeq, &latestGlobalSeq); err != nil {
		if coredb.IsNotFound(err) {
			return notFound("team channel not found")
		}
		return fmt.Errorf("load team channel bounds: %w", err)
	}
	// Reconciliation owns the membership, while notificationState is only the
	// initial default. Existing Human and Agent preferences must remain intact.
	if err := message.EnsureScopeMembership(ctx, execer, channelID, memberID, "public", "", "", latestSeq, latestGlobalSeq, now); err != nil {
		return fmt.Errorf("ensure team channel member: %w", err)
	}
	if _, err := execer.Exec(ctx, `
INSERT IGNORE INTO scope_notification_settings (
  scope_id, member_id, state, created_at, updated_at, unread_generation
) VALUES ($1, $2, $3, $4, $4, 1)
`, trim(channelID), trim(memberID), notificationState, now); err != nil {
		return fmt.Errorf("ensure team channel notification default: %w", err)
	}
	return nil
}

func removeTeamChannelMember(ctx context.Context, execer coredb.QueryExecer, team model.WorkspaceTeam, member model.WorkspaceTeamMember, removedAt time.Time) error {
	channelID := trim(team.ChannelID)
	memberID := trim(member.ActorID)
	if channelID == "" || member.ActorKind != ActorKindAgent || memberID == "" {
		return nil
	}
	return markTeamChannelMemberRemoved(ctx, execer, channelID, memberID, removedAt)
}

func markTeamChannelMemberRemoved(ctx context.Context, execer coredb.QueryExecer, channelID string, memberID string, removedAt time.Time) error {
	now := removedAt.UTC()
	if now.IsZero() {
		now = time.Now().UTC()
	}
	if _, err := execer.Exec(ctx, `
DELETE FROM scope_notification_settings
WHERE scope_id = $1 AND member_id = $2
`, trim(channelID), trim(memberID)); err != nil {
		return fmt.Errorf("delete team channel notification setting: %w", err)
	}
	if _, err := execer.Exec(ctx, `
UPDATE scope_memberships
SET unread_generation = unread_generation + 1,
    removed_at = $3,
    updated_at = $3
WHERE scope_id = $1
  AND member_id = $2
  AND visibility = 'public'
  AND removed_at IS NULL
`, trim(channelID), trim(memberID), now); err != nil {
		return fmt.Errorf("mark team channel member removed: %w", err)
	}
	return nil
}

func archiveTeamChannelProjection(ctx context.Context, execer coredb.QueryExecer, channelID string, archivedAt time.Time) error {
	channelID = trim(channelID)
	if channelID == "" {
		return nil
	}
	now := archivedAt.UTC()
	if now.IsZero() {
		now = time.Now().UTC()
	}
	if _, err := execer.Exec(ctx, `
DELETE FROM scope_notification_settings
WHERE scope_id = $1
`, channelID); err != nil {
		return fmt.Errorf("delete archived team channel notification settings: %w", err)
	}
	if _, err := execer.Exec(ctx, `
UPDATE scope_channel_profiles
SET slug = $2,
    updated_at = $3
WHERE scope_id = $1
  AND slug <> $2
`, channelID, channel.ArchivedScopeProfileSlug(channelID), now); err != nil {
		return fmt.Errorf("release archived team channel slug: %w", err)
	}
	if _, err := execer.Exec(ctx, `
UPDATE scope_memberships
SET unread_generation = unread_generation + 1,
    removed_at = $2,
    archived_at = COALESCE(archived_at, $2),
    updated_at = $2
WHERE scope_id = $1
  AND removed_at IS NULL
`, channelID, now); err != nil {
		return fmt.Errorf("archive team channel memberships: %w", err)
	}
	if _, err := execer.Exec(ctx, `
UPDATE scopes
SET status = 'archived',
    archived_at = COALESCE(archived_at, $2),
    updated_at = $2
WHERE id = $1
`, channelID, now); err != nil {
		return fmt.Errorf("archive team channel projection: %w", err)
	}
	return nil
}
