package organization

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

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

type feishuIdentityLookupClient interface {
	BatchGetUserIDsByEmail(ctx context.Context, email string) ([]FeishuUserIdentity, error)
	BatchGetUserIDsByMobile(ctx context.Context, mobile string) ([]FeishuUserIdentity, error)
}

type feishuAutoJoinTarget struct {
	OrganizationID string
}

type feishuAutoJoinMatch struct {
	OrganizationID string
	MemberID       string
	ExternalUserID string
}

type feishuAutoJoinEvaluation struct {
	OrganizationID string
	Match          *feishuAutoJoinMatch
	Status         string
	Message        string
}

type feishuAutoJoinRebindResult struct {
	Status  string
	Message string
}

type feishuAutoJoinSubject struct {
	SubjectType  string
	StatusLabel  string
	HumanLabel   string
	SubjectValue string
	SubjectHint  string
	Lookup       func(ctx context.Context, client feishuIdentityLookupClient, value string) ([]FeishuUserIdentity, error)
}

func (txs TxStore) AutoJoinOrganizationsForVerifiedEmail(ctx context.Context, user model.User) error {
	if !user.HasVerifiedEmail() {
		return nil
	}

	resolvedEmail := strings.TrimSpace(user.Email)
	if resolvedEmail == "" {
		return nil
	}

	return txs.autoJoinOrganizationsForVerifiedFeishuSubject(ctx, user, feishuAutoJoinSubject{
		SubjectType:  feishuAutoJoinSubjectTypeEmail,
		StatusLabel:  "verified-email",
		HumanLabel:   "verified email",
		SubjectValue: resolvedEmail,
		SubjectHint:  redactFeishuAutoJoinEmailHint(resolvedEmail),
		Lookup: func(ctx context.Context, client feishuIdentityLookupClient, value string) ([]FeishuUserIdentity, error) {
			return client.BatchGetUserIDsByEmail(ctx, value)
		},
	})
}

func (txs TxStore) AutoJoinOrganizationsForVerifiedMobile(ctx context.Context, user model.User) error {
	if !user.HasVerifiedMobile() {
		return nil
	}

	resolvedMobile := strings.TrimSpace(user.Mobile)
	if resolvedMobile == "" {
		return nil
	}

	return txs.autoJoinOrganizationsForVerifiedFeishuSubject(ctx, user, feishuAutoJoinSubject{
		SubjectType:  feishuAutoJoinSubjectTypeMobile,
		StatusLabel:  "verified-mobile",
		HumanLabel:   "verified mobile",
		SubjectValue: resolvedMobile,
		SubjectHint:  authidentity.RedactMobile(resolvedMobile),
		Lookup: func(ctx context.Context, client feishuIdentityLookupClient, value string) ([]FeishuUserIdentity, error) {
			return client.BatchGetUserIDsByMobile(ctx, value)
		},
	})
}

func (txs TxStore) autoJoinOrganizationsForVerifiedFeishuSubject(ctx context.Context, user model.User, subject feishuAutoJoinSubject) error {
	targets, err := txs.listFeishuAutoJoinTargets(ctx)
	if err != nil {
		return err
	}
	if len(targets) == 0 {
		return nil
	}

	attemptedAt := time.Now().UTC()
	recordStatus := func(organizationID string, status string, message string) error {
		return txs.recordFeishuAutoJoinStatus(ctx, organizationID, feishuAutoJoinStatusInput{
			Status:      status,
			SubjectType: subject.SubjectType,
			SubjectHint: subject.SubjectHint,
			Message:     message,
			AttemptedAt: attemptedAt,
		})
	}

	evaluations := make([]feishuAutoJoinEvaluation, 0, len(targets))
	readyMatches := make([]feishuAutoJoinMatch, 0, len(targets))
	for _, target := range targets {
		client, err := txs.newFeishuIdentityLookupClientForOrganization(ctx, target.OrganizationID)
		if err != nil {
			if statusErr := recordStatus(target.OrganizationID, feishuAutoJoinStatusLookupError, fmt.Sprintf("Feishu credentials are unavailable for this organization, so %s auto-join could not run.", subject.StatusLabel)); statusErr != nil {
				return statusErr
			}
			return err
		}

		identities, err := subject.Lookup(ctx, client, subject.SubjectValue)
		if err != nil {
			if statusErr := recordStatus(target.OrganizationID, feishuAutoJoinStatusLookupError, feishuAutoJoinLookupSkippedMessage(subject.StatusLabel)); statusErr != nil {
				return statusErr
			}
			return err
		}

		userIDs := uniqueFeishuUserIDs(identities)
		switch len(userIDs) {
		case 0:
			evaluations = append(evaluations, feishuAutoJoinEvaluation{
				OrganizationID: target.OrganizationID,
				Status:         feishuAutoJoinStatusNoMatch,
				Message:        fmt.Sprintf("Feishu did not return any directory user for this %s.", subject.HumanLabel),
			})
		case 1:
			evaluation, err := txs.evaluateFeishuAutoJoinTarget(ctx, target.OrganizationID, userIDs[0])
			if err != nil {
				if statusErr := recordStatus(target.OrganizationID, feishuAutoJoinStatusLookupError, feishuAutoJoinLookupStateVerificationFailedMessage()); statusErr != nil {
					return statusErr
				}
				return err
			}
			evaluations = append(evaluations, evaluation)
			if evaluation.Match != nil {
				readyMatches = append(readyMatches, *evaluation.Match)
			}
		default:
			evaluations = append(evaluations, feishuAutoJoinEvaluation{
				OrganizationID: target.OrganizationID,
				Status:         feishuAutoJoinStatusAmbiguousMatch,
				Message:        fmt.Sprintf("Feishu returned multiple directory users for this %s, so auto-join was skipped.", subject.HumanLabel),
			})
		}
	}

	switch len(readyMatches) {
	case 0:
		for _, evaluation := range evaluations {
			if err := recordStatus(evaluation.OrganizationID, evaluation.Status, evaluation.Message); err != nil {
				return err
			}
		}
		return nil
	case 1:
		rebindResult, err := txs.rebindFeishuOrganizationMemberToUser(ctx, readyMatches[0], user)
		if err != nil {
			if statusErr := recordStatus(readyMatches[0].OrganizationID, feishuAutoJoinStatusLookupError, feishuAutoJoinRebindFailedMessage()); statusErr != nil {
				return statusErr
			}
			return err
		}

		for _, evaluation := range evaluations {
			status := evaluation.Status
			message := evaluation.Message
			if evaluation.Match != nil && evaluation.OrganizationID == readyMatches[0].OrganizationID {
				status = rebindResult.Status
				message = rebindResult.Message
			}
			if err := recordStatus(evaluation.OrganizationID, status, message); err != nil {
				return err
			}
		}
		return nil
	default:
		for _, evaluation := range evaluations {
			status := evaluation.Status
			message := evaluation.Message
			if evaluation.Match != nil {
				status = feishuAutoJoinStatusAmbiguousMatch
				message = fmt.Sprintf("This %s matched imported Feishu accounts in multiple organizations, so auto-join was skipped.", subject.HumanLabel)
			}
			if err := recordStatus(evaluation.OrganizationID, status, message); err != nil {
				return err
			}
		}
		return nil
	}
}

func (txs TxStore) newFeishuIdentityLookupClientForOrganization(ctx context.Context, organizationID string) (feishuIdentityLookupClient, error) {
	credentials, err := txs.resolveOrganizationFeishuCredentials(ctx, txs.db, organizationID)
	if err != nil {
		return nil, err
	}
	return NewFeishuDirectoryHTTPClient(FeishuDirectoryHTTPClientOptions{
		BaseURL:    txs.feishuBaseURL,
		AppID:      credentials.AppID,
		AppSecret:  credentials.AppSecret,
		HTTPClient: txs.feishuHTTPClient,
	})
}

func (txs TxStore) listFeishuAutoJoinTargets(ctx context.Context) ([]feishuAutoJoinTarget, error) {
	rows, err := txs.db.Query(ctx, `
SELECT organization_id
FROM organization_settings
WHERE status = 'active'
  AND directory_provider = $1
  AND directory_backend <> $2
ORDER BY organization_id ASC
`, organizationProviderFeishu, organizationDirectoryBackendEmbedded)
	if err != nil {
		return nil, fmt.Errorf("list feishu auto join targets: %w", err)
	}
	defer rows.Close()

	targets := make([]feishuAutoJoinTarget, 0)
	for rows.Next() {
		var target feishuAutoJoinTarget
		if err := rows.Scan(&target.OrganizationID); err != nil {
			return nil, fmt.Errorf("scan feishu auto join target: %w", err)
		}
		targets = append(targets, target)
	}
	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("iterate feishu auto join targets: %w", err)
	}
	return targets, nil
}

func (txs TxStore) evaluateFeishuAutoJoinTarget(ctx context.Context, organizationID string, externalUserID string) (feishuAutoJoinEvaluation, error) {
	evaluation := feishuAutoJoinEvaluation{
		OrganizationID: organizationID,
		Status:         feishuAutoJoinStatusDirectoryUserNotImported,
		Message:        "Feishu resolved a directory user, but this organization has not imported that user yet. Run Feishu sync first.",
	}

	account, found, err := txs.loadOrganizationMemberByExternalIdentity(ctx, txs.db, organizationID, organizationProviderFeishu, externalUserID)
	if err != nil {
		return feishuAutoJoinEvaluation{}, err
	}
	if !found {
		return evaluation, nil
	}
	if account.Status != organizationMemberStatusActive {
		evaluation.Status = feishuAutoJoinStatusDirectoryMemberInactive
		evaluation.Message = "The imported Feishu account is inactive in this organization, so auto-join was skipped."
		return evaluation, nil
	}

	member, err := txs.loadOrganizationMemberByAccountAnyStatus(ctx, txs.db, organizationID, account.ID)
	if err != nil {
		if errors.Is(err, ErrNotFound) {
			evaluation.Status = feishuAutoJoinStatusDirectoryMemberInactive
			evaluation.Message = "The imported Feishu member record is missing or inactive in this organization, so auto-join was skipped."
			return evaluation, nil
		}
		return feishuAutoJoinEvaluation{}, err
	}
	if member.Status != organizationMemberStatusActive {
		evaluation.Status = feishuAutoJoinStatusDirectoryMemberInactive
		evaluation.Message = "The imported Feishu member is inactive in this organization, so auto-join was skipped."
		return evaluation, nil
	}

	evaluation.Match = &feishuAutoJoinMatch{
		OrganizationID: organizationID,
		MemberID:       account.ID,
		ExternalUserID: externalUserID,
	}
	evaluation.Status = feishuAutoJoinStatusSuccess
	evaluation.Message = feishuAutoJoinPreparedMessage()
	return evaluation, nil
}

func uniqueFeishuUserIDs(identities []FeishuUserIdentity) []string {
	if len(identities) == 0 {
		return nil
	}

	resolved := make([]string, 0, len(identities))
	seen := make(map[string]struct{}, len(identities))
	for _, identity := range identities {
		userID := strings.TrimSpace(identity.UserID)
		if userID == "" {
			continue
		}
		if _, exists := seen[userID]; exists {
			continue
		}
		seen[userID] = struct{}{}
		resolved = append(resolved, userID)
	}
	return resolved
}

func (txs TxStore) rebindFeishuOrganizationMemberToUser(ctx context.Context, match feishuAutoJoinMatch, user model.User) (feishuAutoJoinRebindResult, error) {
	return withTxValue(ctx, txs.db, func(tx coredb.Tx) (feishuAutoJoinRebindResult, error) {
		account, found, err := txs.loadOrganizationMemberByExternalIdentity(ctx, tx, match.OrganizationID, organizationProviderFeishu, match.ExternalUserID)
		if err != nil {
			return feishuAutoJoinRebindResult{}, err
		}
		if !found || account.Status != organizationMemberStatusActive {
			return feishuAutoJoinRebindResult{
				Status:  feishuAutoJoinStatusDirectoryMemberInactive,
				Message: "The imported Feishu account is no longer active, so auto-join was skipped.",
			}, nil
		}

		member, err := txs.loadOrganizationMemberByAccountAnyStatus(ctx, tx, match.OrganizationID, account.ID)
		if err != nil {
			if errors.Is(err, ErrNotFound) {
				return feishuAutoJoinRebindResult{
					Status:  feishuAutoJoinStatusDirectoryMemberInactive,
					Message: "The imported Feishu member record is no longer available, so auto-join was skipped.",
				}, nil
			}
			return feishuAutoJoinRebindResult{}, err
		}
		if member.Status != organizationMemberStatusActive {
			return feishuAutoJoinRebindResult{
				Status:  feishuAutoJoinStatusDirectoryMemberInactive,
				Message: "The imported Feishu member is inactive, so auto-join was skipped.",
			}, nil
		}

		existingMember, hasExistingMember, err := txs.loadOrganizationMemberByUserIDAnyStatus(ctx, tx, match.OrganizationID, user.ID)
		if err != nil {
			return feishuAutoJoinRebindResult{}, err
		}
		if hasExistingMember && existingMember.ID != account.ID {
			return feishuAutoJoinRebindResult{
				Status:  feishuAutoJoinStatusConflictingMember,
				Message: feishuAutoJoinConflictingUserMessage(),
			}, nil
		}

		now := time.Now().UTC()
		previousUserID := strings.TrimSpace(account.UserID)
		if previousUserID != "" && previousUserID != user.ID {
			if err := txs.cleanupSyntheticFeishuWorkspaceMembersForRebind(ctx, tx, match.OrganizationID, account.ID, previousUserID); err != nil {
				return feishuAutoJoinRebindResult{}, err
			}
		}

		if _, err := tx.Exec(ctx, `
UPDATE organization_members
SET user_id = $3,
    status = 'active',
    disabled_at = NULL,
    updated_at = $4
WHERE organization_id = $1
  AND id = $2
		`, match.OrganizationID, account.ID, user.ID, now); err != nil {
			if isUniqueViolation(err) {
				return feishuAutoJoinRebindResult{
					Status:  feishuAutoJoinStatusConflictingMember,
					Message: "A conflicting organization member binding already exists, so auto-join was skipped.",
				}, nil
			}
			return feishuAutoJoinRebindResult{}, fmt.Errorf("rebind feishu organization member: %w", err)
		}
		if err := txs.ensureOrganizationDefaultTeamMembershipForMember(ctx, tx, match.OrganizationID, account.ID); err != nil {
			return feishuAutoJoinRebindResult{}, err
		}

		if err := txs.reconcileOrganizationMemberAllWorkspaceProjections(ctx, tx, match.OrganizationID, account.ID); err != nil {
			return feishuAutoJoinRebindResult{}, err
		}

		return feishuAutoJoinRebindResult{
			Status:  feishuAutoJoinStatusSuccess,
			Message: feishuAutoJoinJoinedMessage(),
		}, nil
	})
}

func feishuAutoJoinLookupSkippedMessage(statusLabel string) string {
	return fmt.Sprintf("Feishu lookup failed, so AgentTeam skipped %s auto-join.", statusLabel)
}

func feishuAutoJoinLookupStateVerificationFailedMessage() string {
	return "AgentTeam could not verify the imported Feishu account state for auto-join."
}

func feishuAutoJoinRebindFailedMessage() string {
	return "AgentTeam failed while rebinding the imported Feishu account to the verified user."
}

func feishuAutoJoinPreparedMessage() string {
	return "Matched an imported Feishu account and prepared AgentTeam auto-join."
}

func feishuAutoJoinConflictingUserMessage() string {
	return "This verified AgentTeam user is already bound to a different organization member, so auto-join was skipped."
}

func feishuAutoJoinJoinedMessage() string {
	return "Matched the imported Feishu account and joined the verified AgentTeam user to the organization."
}

func (txs TxStore) cleanupSyntheticFeishuWorkspaceMembersForRebind(ctx context.Context, tx coredb.Tx, organizationID string, accountID string, previousUserID string) error {
	previousUser, err := txs.core.FindUserByID(ctx, previousUserID)
	if err != nil {
		if errors.Is(err, ErrNotFound) {
			return nil
		}
		return err
	}
	if !strings.HasSuffix(strings.ToLower(strings.TrimSpace(previousUser.Email)), "@directory.invalid") {
		return nil
	}

	rows, err := tx.Query(ctx, `
SELECT workspace_id
FROM organization_workspace_member_projections
WHERE organization_id = $1
  AND organization_member_id = $2
  AND member_created_by_org = TRUE
ORDER BY workspace_id ASC
`, organizationID, accountID)
	if err != nil {
		return fmt.Errorf("list feishu workspace projections for cleanup: %w", err)
	}
	defer rows.Close()

	workspaceIDs := make([]string, 0)
	for rows.Next() {
		var workspaceID string
		if err := rows.Scan(&workspaceID); err != nil {
			return fmt.Errorf("scan feishu workspace projection cleanup: %w", err)
		}
		workspaceIDs = append(workspaceIDs, workspaceID)
	}
	if err := rows.Err(); err != nil {
		return fmt.Errorf("iterate feishu workspace projection cleanup: %w", err)
	}

	for _, workspaceID := range workspaceIDs {
		if _, err := txs.core.DeactivateHumanWorkspaceMemberInTx(ctx, tx, workspaceID, previousUserID); err != nil && !errors.Is(err, ErrNotFound) {
			return err
		}
	}
	return nil
}
