package organization

import (
	"context"
	"database/sql"
	"fmt"
	"strings"
	"time"

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

type organizationSettingsRecord struct {
	OrganizationSettingsView
}

func (record organizationSettingsRecord) view(hasFeishuServerConfig bool) OrganizationSettingsView {
	view := record.OrganizationSettingsView
	view.Feishu.HasServerConfig = hasFeishuServerConfig
	return view
}

func normalizeOrganizationDirectoryBackend(directoryBackend string) (string, error) {
	switch strings.TrimSpace(directoryBackend) {
	case organizationDirectoryBackendEmbedded:
		return organizationDirectoryBackendEmbedded, nil
	case organizationDirectoryBackendExternalShadow:
		return organizationDirectoryBackendExternalShadow, nil
	case organizationDirectoryBackendExternalAuthority:
		return organizationDirectoryBackendExternalAuthority, nil
	default:
		return "", badRequest("invalid organization directory backend")
	}
}

func normalizeOrganizationProvider(providerKind string, provider string) (string, error) {
	switch strings.TrimSpace(provider) {
	case "":
		return "", nil
	case organizationProviderFeishu:
		return organizationProviderFeishu, nil
	default:
		return "", badRequest(fmt.Sprintf("invalid organization %s provider", providerKind))
	}
}

func (txs TxStore) GetOrganizationSettingsForUser(ctx context.Context, userID string, organizationID string) (OrganizationSettingsView, error) {
	if _, err := txs.requireActiveOrganizationMembership(ctx, txs.db, userID, organizationID); err != nil {
		return OrganizationSettingsView{}, err
	}
	return txs.loadOrganizationSettingsView(ctx, txs.db, organizationID)
}

func (txs TxStore) UpdateOrganizationSettingsForUser(ctx context.Context, userID string, organizationID string, directoryBackend string) (OrganizationSettingsView, error) {
	return txs.PatchOrganizationSettingsForUser(ctx, userID, organizationID, UpdateOrganizationSettingsParams{
		DirectoryBackend: &directoryBackend,
	})
}

func (txs TxStore) PatchOrganizationSettingsForUser(ctx context.Context, userID string, organizationID string, params UpdateOrganizationSettingsParams) (OrganizationSettingsView, error) {
	if _, err := txs.requireActiveOrganizationAdmin(ctx, txs.db, userID, organizationID); err != nil {
		return OrganizationSettingsView{}, err
	}

	return withTxValue(ctx, txs.db, func(tx coredb.Tx) (OrganizationSettingsView, error) {
		currentSettings, err := txs.loadOrganizationSettingsRecord(ctx, tx, organizationID)
		if err != nil {
			return OrganizationSettingsView{}, err
		}

		nextSettings, refreshSyncModes, changed, err := resolveOrganizationSettingsUpdate(currentSettings, params)
		if err != nil {
			return OrganizationSettingsView{}, err
		}
		if !changed {
			return txs.loadOrganizationSettingsView(ctx, tx, organizationID)
		}

		if err := txs.updateOrganizationSettingsInTx(ctx, tx, currentSettings, nextSettings, refreshSyncModes); err != nil {
			return OrganizationSettingsView{}, err
		}

		settings, err := txs.loadOrganizationSettingsView(ctx, tx, organizationID)
		if err != nil {
			return OrganizationSettingsView{}, err
		}

		return settings, nil
	})
}

func resolveOrganizationSettingsUpdate(current organizationSettingsRecord, params UpdateOrganizationSettingsParams) (organizationSettingsRecord, bool, bool, error) {
	next := current
	changed := false

	if params.DirectoryBackend != nil {
		resolvedBackend, err := normalizeOrganizationDirectoryBackend(*params.DirectoryBackend)
		if err != nil {
			return organizationSettingsRecord{}, false, false, err
		}
		if next.DirectoryBackend != resolvedBackend {
			changed = true
		}
		next.DirectoryBackend = resolvedBackend
	}

	if params.AuthProvider != nil {
		resolvedProvider, err := normalizeOrganizationProvider("auth", *params.AuthProvider)
		if err != nil {
			return organizationSettingsRecord{}, false, false, err
		}
		if next.AuthProvider != resolvedProvider {
			changed = true
		}
		next.AuthProvider = resolvedProvider
	}

	if params.DirectoryProvider != nil {
		resolvedProvider, err := normalizeOrganizationProvider("directory", *params.DirectoryProvider)
		if err != nil {
			return organizationSettingsRecord{}, false, false, err
		}
		if next.DirectoryProvider != resolvedProvider {
			changed = true
		}
		next.DirectoryProvider = resolvedProvider
	}

	if params.Feishu != nil {
		if params.Feishu.TenantKey != nil {
			resolved := strings.TrimSpace(*params.Feishu.TenantKey)
			if next.Feishu.TenantKey != resolved {
				changed = true
			}
			next.Feishu.TenantKey = resolved
		}
		if params.Feishu.RootDepartmentID != nil {
			resolved := strings.TrimSpace(*params.Feishu.RootDepartmentID)
			if next.Feishu.RootDepartmentID != resolved {
				changed = true
			}
			next.Feishu.RootDepartmentID = resolved
		}
	}

	refreshSyncModes := current.DirectoryBackend != next.DirectoryBackend
	return next, refreshSyncModes, changed, nil
}

func (txs TxStore) loadOrganizationSettingsView(ctx context.Context, queryer queryRower, organizationID string) (OrganizationSettingsView, error) {
	record, err := txs.loadOrganizationSettingsRecord(ctx, queryer, organizationID)
	if err != nil {
		return OrganizationSettingsView{}, err
	}
	hasUsableFeishuCredentials, err := txs.hasUsableOrganizationFeishuCredentials(ctx, queryer, organizationID)
	if err != nil {
		return OrganizationSettingsView{}, err
	}
	view := record.view(hasUsableFeishuCredentials)

	lastSync, err := txs.loadLatestOrganizationDirectorySyncRunSummary(ctx, queryer, organizationID)
	if err != nil {
		return OrganizationSettingsView{}, err
	}
	view.Feishu.LastSync = lastSync
	return view, nil
}

func (txs TxStore) loadOrganizationSettingsRecord(ctx context.Context, queryer queryRower, organizationID string) (organizationSettingsRecord, error) {
	row := queryer.QueryRow(ctx, `
SELECT organization_id,
       directory_backend,
       directory_revision,
       auth_provider,
       directory_provider,
       feishu_tenant_key,
       feishu_root_department_id,
       feishu_last_auto_join_status,
       feishu_last_auto_join_subject_type,
       feishu_last_auto_join_subject_hint,
       feishu_last_auto_join_message,
       feishu_last_auto_join_attempted_at
FROM organization_settings
WHERE organization_id = $1
`, organizationID)

	var settings organizationSettingsRecord
	var feishuLastAutoJoinStatus string
	var feishuLastAutoJoinSubjectType string
	var feishuLastAutoJoinSubjectHint string
	var feishuLastAutoJoinMessage string
	var feishuLastAutoJoinAttemptedAt sql.NullTime
	if err := row.Scan(
		&settings.OrganizationID,
		&settings.DirectoryBackend,
		&settings.DirectoryRevision,
		&settings.AuthProvider,
		&settings.DirectoryProvider,
		&settings.Feishu.TenantKey,
		&settings.Feishu.RootDepartmentID,
		&feishuLastAutoJoinStatus,
		&feishuLastAutoJoinSubjectType,
		&feishuLastAutoJoinSubjectHint,
		&feishuLastAutoJoinMessage,
		&feishuLastAutoJoinAttemptedAt,
	); err != nil {
		if coredb.IsNotFound(err) {
			return organizationSettingsRecord{}, ErrNotFound
		}
		return organizationSettingsRecord{}, fmt.Errorf("load organization settings: %w", err)
	}
	settings.Feishu.LastAutoJoin = buildOrganizationFeishuAutoJoinStatusView(
		feishuLastAutoJoinStatus,
		feishuLastAutoJoinSubjectType,
		feishuLastAutoJoinSubjectHint,
		feishuLastAutoJoinMessage,
		feishuLastAutoJoinAttemptedAt,
	)
	return settings, nil
}

func buildOrganizationFeishuAutoJoinStatusView(status string, subjectType string, subjectHint string, message string, attemptedAt sql.NullTime) *OrganizationFeishuAutoJoinStatusView {
	if strings.TrimSpace(status) == "" && !attemptedAt.Valid {
		return nil
	}

	var attemptedAtValue *time.Time
	if attemptedAt.Valid {
		value := attemptedAt.Time
		attemptedAtValue = &value
	}

	return &OrganizationFeishuAutoJoinStatusView{
		Status:      strings.TrimSpace(status),
		SubjectType: strings.TrimSpace(subjectType),
		SubjectHint: strings.TrimSpace(subjectHint),
		Message:     strings.TrimSpace(message),
		AttemptedAt: attemptedAtValue,
	}
}

func (txs TxStore) loadLatestOrganizationDirectorySyncRunSummary(ctx context.Context, queryer queryRower, organizationID string) (*OrganizationDirectorySyncRunSummaryView, error) {
	row := queryer.QueryRow(ctx, `
SELECT id,
       external_provider,
       sync_source,
       status,
       directory_revision,
       started_at,
       completed_at
FROM organization_directory_sync_runs
WHERE organization_id = $1
ORDER BY started_at DESC, id DESC
LIMIT 1
`, organizationID)

	var summary OrganizationDirectorySyncRunSummaryView
	var startedAt time.Time
	var completedAt sql.NullTime
	if err := row.Scan(
		&summary.SyncRunID,
		&summary.ExternalProvider,
		&summary.SyncSource,
		&summary.Status,
		&summary.DirectoryRevision,
		&startedAt,
		&completedAt,
	); err != nil {
		if coredb.IsNotFound(err) {
			return nil, nil
		}
		return nil, fmt.Errorf("load latest organization directory sync run summary: %w", err)
	}

	summary.StartedAt = &startedAt
	if completedAt.Valid {
		value := completedAt.Time
		summary.CompletedAt = &value
	}
	return &summary, nil
}

func (txs TxStore) updateOrganizationSettingsInTx(ctx context.Context, tx coredb.Tx, currentSettings organizationSettingsRecord, nextSettings organizationSettingsRecord, refreshSyncModes bool) error {
	syncMode, err := resolveOrganizationSyncMode(nextSettings.DirectoryBackend)
	if err != nil {
		return err
	}

	now := time.Now().UTC()
	commandTag, err := tx.Exec(ctx, `
UPDATE organization_settings
SET directory_backend = $2,
    directory_revision = $3,
    auth_provider = $4,
    directory_provider = $5,
    feishu_tenant_key = $6,
    feishu_root_department_id = $7,
    updated_at = $8
WHERE organization_id = $1
`, currentSettings.OrganizationID, nextSettings.DirectoryBackend, currentSettings.DirectoryRevision+1, nextSettings.AuthProvider, nextSettings.DirectoryProvider, nextSettings.Feishu.TenantKey, nextSettings.Feishu.RootDepartmentID, now)
	if err != nil {
		return fmt.Errorf("update organization settings: %w", err)
	}
	if commandTag.RowsAffected() == 0 {
		return ErrNotFound
	}

	if refreshSyncModes {
		if err := txs.refreshOrganizationObjectSyncModesInTx(ctx, tx, currentSettings.OrganizationID, syncMode, now); err != nil {
			return err
		}
	}
	return nil
}

func (txs TxStore) refreshOrganizationObjectSyncModesInTx(ctx context.Context, tx coredb.Tx, organizationID string, syncMode string, updatedAt time.Time) error {
	if _, err := tx.Exec(ctx, `
UPDATE organization_teams
SET sync_mode = CASE
        WHEN COALESCE(external_provider, '') <> ''
          OR COALESCE(external_id, '') <> ''
          OR synced_at IS NOT NULL THEN $2
        ELSE 'local'
    END,
    updated_at = $3
WHERE organization_id = $1
`, organizationID, syncMode, updatedAt); err != nil {
		return fmt.Errorf("refresh organization team sync modes: %w", err)
	}

	if _, err := tx.Exec(ctx, `
UPDATE organization_units
SET sync_mode = CASE
        WHEN COALESCE(external_provider, '') <> ''
          OR COALESCE(external_id, '') <> ''
          OR synced_at IS NOT NULL THEN $2
        ELSE 'local'
    END,
    updated_at = $3
WHERE organization_id = $1
`, organizationID, syncMode, updatedAt); err != nil {
		return fmt.Errorf("refresh organization unit sync modes: %w", err)
	}

	if _, err := tx.Exec(ctx, `
UPDATE organization_members
SET sync_mode = CASE
        WHEN COALESCE(external_provider, '') <> ''
          OR COALESCE(external_id, '') <> ''
          OR synced_at IS NOT NULL THEN $2
        ELSE 'local'
    END,
    updated_at = $3
WHERE organization_id = $1
`, organizationID, syncMode, updatedAt); err != nil {
		return fmt.Errorf("refresh organization member sync modes: %w", err)
	}

	return nil
}
