package organization

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

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

func normalizeOrganizationAuthSubjectKey(subjectKey string) (string, error) {
	switch strings.TrimSpace(subjectKey) {
	case organizationAuthSubjectKeySubject:
		return organizationAuthSubjectKeySubject, nil
	case organizationAuthSubjectKeyOpenID:
		return organizationAuthSubjectKeyOpenID, nil
	case organizationAuthSubjectKeyUnionID:
		return organizationAuthSubjectKeyUnionID, nil
	default:
		return "", badRequest("invalid organization auth binding subject key")
	}
}

func (txs TxStore) UpsertOrganizationMemberAuthBinding(ctx context.Context, organizationID string, memberID string, params UpsertOrganizationAuthBindingParams) (OrganizationAuthBindingView, error) {
	authProvider, err := normalizeOrganizationProvider("auth", params.AuthProvider)
	if err != nil {
		return OrganizationAuthBindingView{}, err
	}
	if authProvider == "" {
		return OrganizationAuthBindingView{}, badRequest("organization auth binding provider is required")
	}

	subjectKey, err := normalizeOrganizationAuthSubjectKey(params.SubjectKey)
	if err != nil {
		return OrganizationAuthBindingView{}, err
	}

	subjectValue := strings.TrimSpace(params.SubjectValue)
	if subjectValue == "" {
		return OrganizationAuthBindingView{}, badRequest("organization auth binding subject value is required")
	}

	providerTenantID := strings.TrimSpace(params.ProviderTenantID)

	return withTxValue(ctx, txs.db, func(tx coredb.Tx) (OrganizationAuthBindingView, error) {
		if _, err := txs.loadActiveOrganizationMember(ctx, tx, organizationID, memberID); err != nil {
			return OrganizationAuthBindingView{}, err
		}

		existingBySubject, found, err := txs.findOrganizationAuthBinding(ctx, tx, organizationID, authProvider, subjectKey, subjectValue)
		if err != nil {
			return OrganizationAuthBindingView{}, err
		}
		if found && existingBySubject.OrganizationMemberID != memberID {
			return OrganizationAuthBindingView{}, conflict("organization auth identity already bound to another member")
		}

		now := time.Now().UTC()
		existingByMember, found, err := txs.loadOrganizationMemberAuthBinding(ctx, tx, organizationID, memberID, authProvider, subjectKey)
		if err != nil {
			return OrganizationAuthBindingView{}, err
		}

		if found {
			_, err = tx.Exec(ctx, `
UPDATE organization_auth_bindings
SET subject_value = $5,
    provider_tenant_id = $6,
    updated_at = $7
WHERE id = $1
  AND organization_id = $2
  AND organization_member_id = $3
  AND auth_provider = $4
`, existingByMember.ID, organizationID, memberID, authProvider, subjectValue, providerTenantID, now)
			if err != nil {
				if isUniqueViolation(err) {
					return OrganizationAuthBindingView{}, conflict("organization auth identity already bound to another member")
				}
				return OrganizationAuthBindingView{}, fmt.Errorf("update organization auth binding: %w", err)
			}
		} else {
			bindingID, err := identifiers.NewEntityID("orgab")
			if err != nil {
				return OrganizationAuthBindingView{}, fmt.Errorf("generate organization auth binding id: %w", err)
			}
			if _, err := tx.Exec(ctx, `
INSERT INTO organization_auth_bindings (
    id,
    organization_id,
    organization_member_id,
    auth_provider,
    subject_key,
    subject_value,
    provider_tenant_id,
    created_at,
    updated_at
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $8)
`, bindingID, organizationID, memberID, authProvider, subjectKey, subjectValue, providerTenantID, now); err != nil {
				if isUniqueViolation(err) {
					return OrganizationAuthBindingView{}, conflict("organization auth identity already bound to another member")
				}
				return OrganizationAuthBindingView{}, fmt.Errorf("insert organization auth binding: %w", err)
			}
		}

		binding, found, err := txs.loadOrganizationMemberAuthBinding(ctx, tx, organizationID, memberID, authProvider, subjectKey)
		if err != nil {
			return OrganizationAuthBindingView{}, err
		}
		if !found {
			return OrganizationAuthBindingView{}, ErrNotFound
		}

		return binding, nil
	})
}

func (txs TxStore) ListOrganizationMemberAuthBindings(ctx context.Context, organizationID string, memberID string) ([]OrganizationAuthBindingView, error) {
	if _, err := txs.loadActiveOrganizationMember(ctx, txs.db, organizationID, memberID); err != nil {
		return nil, err
	}

	rows, err := txs.db.Query(ctx, `
SELECT id,
       organization_id,
       organization_member_id,
       auth_provider,
       subject_key,
       subject_value,
       provider_tenant_id,
       created_at,
       updated_at
FROM organization_auth_bindings
WHERE organization_id = $1
  AND organization_member_id = $2
ORDER BY auth_provider ASC, subject_key ASC, created_at ASC
`, organizationID, memberID)
	if err != nil {
		return nil, fmt.Errorf("list organization auth bindings: %w", err)
	}
	defer rows.Close()

	bindings := make([]OrganizationAuthBindingView, 0)
	for rows.Next() {
		var binding OrganizationAuthBindingView
		if err := rows.Scan(
			&binding.ID,
			&binding.OrganizationID,
			&binding.OrganizationMemberID,
			&binding.AuthProvider,
			&binding.SubjectKey,
			&binding.SubjectValue,
			&binding.ProviderTenantID,
			&binding.CreatedAt,
			&binding.UpdatedAt,
		); err != nil {
			return nil, fmt.Errorf("scan organization auth binding: %w", err)
		}
		bindings = append(bindings, binding)
	}
	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("iterate organization auth bindings: %w", err)
	}
	return bindings, nil
}

func (txs TxStore) FindOrganizationAuthBinding(ctx context.Context, organizationID string, authProvider string, subjectKey string, subjectValue string) (OrganizationAuthBindingView, bool, error) {
	resolvedProvider, err := normalizeOrganizationProvider("auth", authProvider)
	if err != nil {
		return OrganizationAuthBindingView{}, false, err
	}
	if resolvedProvider == "" {
		return OrganizationAuthBindingView{}, false, badRequest("organization auth binding provider is required")
	}

	resolvedSubjectKey, err := normalizeOrganizationAuthSubjectKey(subjectKey)
	if err != nil {
		return OrganizationAuthBindingView{}, false, err
	}

	trimmedSubjectValue := strings.TrimSpace(subjectValue)
	if trimmedSubjectValue == "" {
		return OrganizationAuthBindingView{}, false, badRequest("organization auth binding subject value is required")
	}

	return txs.findOrganizationAuthBinding(ctx, txs.db, organizationID, resolvedProvider, resolvedSubjectKey, trimmedSubjectValue)
}

func (txs TxStore) loadOrganizationMemberAuthBinding(ctx context.Context, queryer queryRower, organizationID string, memberID string, authProvider string, subjectKey string) (OrganizationAuthBindingView, bool, error) {
	row := queryer.QueryRow(ctx, `
SELECT id,
       organization_id,
       organization_member_id,
       auth_provider,
       subject_key,
       subject_value,
       provider_tenant_id,
       created_at,
       updated_at
FROM organization_auth_bindings
WHERE organization_id = $1
  AND organization_member_id = $2
  AND auth_provider = $3
  AND subject_key = $4
`, organizationID, memberID, authProvider, subjectKey)

	var binding OrganizationAuthBindingView
	if err := row.Scan(
		&binding.ID,
		&binding.OrganizationID,
		&binding.OrganizationMemberID,
		&binding.AuthProvider,
		&binding.SubjectKey,
		&binding.SubjectValue,
		&binding.ProviderTenantID,
		&binding.CreatedAt,
		&binding.UpdatedAt,
	); err != nil {
		if coredb.IsNotFound(err) {
			return OrganizationAuthBindingView{}, false, nil
		}
		return OrganizationAuthBindingView{}, false, fmt.Errorf("load organization auth binding: %w", err)
	}
	return binding, true, nil
}

func (txs TxStore) findOrganizationAuthBinding(ctx context.Context, queryer queryRower, organizationID string, authProvider string, subjectKey string, subjectValue string) (OrganizationAuthBindingView, bool, error) {
	row := queryer.QueryRow(ctx, `
SELECT id,
       organization_id,
       organization_member_id,
       auth_provider,
       subject_key,
       subject_value,
       provider_tenant_id,
       created_at,
       updated_at
FROM organization_auth_bindings
WHERE organization_id = $1
  AND auth_provider = $2
  AND subject_key = $3
  AND subject_value = $4
`, organizationID, authProvider, subjectKey, subjectValue)

	var binding OrganizationAuthBindingView
	if err := row.Scan(
		&binding.ID,
		&binding.OrganizationID,
		&binding.OrganizationMemberID,
		&binding.AuthProvider,
		&binding.SubjectKey,
		&binding.SubjectValue,
		&binding.ProviderTenantID,
		&binding.CreatedAt,
		&binding.UpdatedAt,
	); err != nil {
		if coredb.IsNotFound(err) {
			return OrganizationAuthBindingView{}, false, nil
		}
		return OrganizationAuthBindingView{}, false, fmt.Errorf("find organization auth binding: %w", err)
	}
	return binding, true, nil
}
