package handler

import (
	"baron-sso-backend/internal/domain"
	"baron-sso-backend/internal/repository"
	"baron-sso-backend/internal/service"
	"context"
	"fmt"
	"log/slog"
	"maps"
	"strings"
)

const tenantAccessCleanupClientPageSize = 500

func cleanupDeletedTenantReferences(ctx context.Context, hydra *service.HydraAdminService, consentRepo repository.ClientConsentRepository, ketoOutbox repository.KetoOutboxRepository, deletedTenantIDs []string) error {
	if hydra == nil {
		return nil
	}

	deletedTenantSet := make(map[string]struct{}, len(deletedTenantIDs))
	for _, tenantID := range deletedTenantIDs {
		tenantID = strings.TrimSpace(tenantID)
		if tenantID == "" {
			continue
		}
		deletedTenantSet[tenantID] = struct{}{}
	}
	if len(deletedTenantSet) == 0 {
		return nil
	}

	for offset := 0; ; offset += tenantAccessCleanupClientPageSize {
		clients, err := hydra.ListClients(ctx, tenantAccessCleanupClientPageSize, offset)
		if err != nil {
			return fmt.Errorf("failed to list hydra clients for tenant cleanup: %w", err)
		}

		for _, client := range clients {
			beforeMetadata := maps.Clone(client.Metadata)
			updatedMetadata, changed, removedOwnerTenantID := pruneDeletedTenantReferences(beforeMetadata, deletedTenantSet)
			if !changed {
				continue
			}

			updatedClient := client
			updatedClient.Metadata = updatedMetadata
			if _, err := hydra.UpdateClient(ctx, client.ClientID, updatedClient); err != nil {
				return fmt.Errorf("failed to update hydra client %s during tenant cleanup: %w", client.ClientID, err)
			}
			if removedOwnerTenantID != "" {
				if err := enqueueDeletedTenantRelyingPartyParentCleanup(ctx, ketoOutbox, client.ClientID, removedOwnerTenantID); err != nil {
					return fmt.Errorf("failed to cleanup RP parent relation for client %s during tenant cleanup: %w", client.ClientID, err)
				}
			}

			if tenantAccessPolicyChanged(beforeMetadata, updatedMetadata) {
				if err := revokeClientConsentsForPolicyChange(ctx, hydra, consentRepo, client.ClientID); err != nil {
					return fmt.Errorf("failed to revoke consent sessions for client %s during tenant cleanup: %w", client.ClientID, err)
				}
			}
		}

		if len(clients) < tenantAccessCleanupClientPageSize {
			return nil
		}
	}
}

func pruneDeletedTenantReferences(metadata map[string]any, deletedTenantSet map[string]struct{}) (map[string]any, bool, string) {
	if len(deletedTenantSet) == 0 {
		return metadata, false, ""
	}

	ownerTenantID := normalizeMetadataString(metadata["tenant_id"])
	_, ownerDeleted := deletedTenantSet[ownerTenantID]

	allowedTenants := normalizeMetadataStringSlice(metadata[clientAllowedTenantsKey])
	filtered := make([]string, 0, len(allowedTenants))
	for _, tenantID := range allowedTenants {
		if _, ok := deletedTenantSet[tenantID]; ok {
			continue
		}
		filtered = append(filtered, tenantID)
	}
	allowedChanged := len(filtered) != len(allowedTenants)

	if !ownerDeleted && !allowedChanged {
		return metadata, false, ""
	}

	updated := maps.Clone(metadata)
	if ownerDeleted {
		delete(updated, "tenant_id")
	}

	if len(filtered) == 0 {
		delete(updated, clientAllowedTenantsKey)
		updated[clientTenantAccessRestrictedKey] = false
		return updated, true, ownerTenantID
	}

	updated[clientAllowedTenantsKey] = uniqueSortedStrings(filtered)
	updated[clientTenantAccessRestrictedKey] = true
	return updated, true, ownerTenantID
}

func enqueueDeletedTenantRelyingPartyParentCleanup(ctx context.Context, ketoOutbox repository.KetoOutboxRepository, clientID, tenantID string) error {
	if ketoOutbox == nil {
		return nil
	}
	clientID = strings.TrimSpace(clientID)
	tenantID = strings.TrimSpace(tenantID)
	if clientID == "" || tenantID == "" {
		return nil
	}
	return ketoOutbox.Create(ctx, &domain.KetoOutbox{
		Namespace: "RelyingParty",
		Object:    clientID,
		Relation:  "parents",
		Subject:   "Tenant:" + tenantID,
		Action:    domain.KetoOutboxActionDelete,
	})
}

func revokeClientConsentsForPolicyChange(ctx context.Context, hydra *service.HydraAdminService, consentRepo repository.ClientConsentRepository, clientID string) error {
	if consentRepo == nil || hydra == nil {
		return nil
	}

	subjects, err := consentRepo.ListSubjectsByClient(ctx, clientID)
	if err != nil {
		return err
	}

	for _, subject := range subjects {
		subject = strings.TrimSpace(subject)
		if subject == "" {
			continue
		}
		if err := hydra.RevokeConsentSessions(ctx, subject, clientID); err != nil {
			return err
		}
	}

	return consentRepo.DeleteByClient(ctx, clientID)
}

func logTenantCleanupFailure(err error, deletedTenantIDs []string) {
	if err == nil {
		return
	}
	slog.Error("Failed to cleanup RP tenant restrictions after tenant deletion", "tenant_ids", deletedTenantIDs, "error", err)
}
