// packages/backend/src/iam-policy/ability.ts
import { createMongoAbility, MongoAbility, RawRuleOf } from '@casl/ability';
import { CaslPolicy, AppActions, AppSubjects } from '@open-archiver/types';
import { ingestionSources, archivedEmails, users, roles } from '../database/schema';
import { InferSelectModel } from 'drizzle-orm';

// Define the application's ability type
export type AppAbility = MongoAbility<[AppActions, AppSubjects]>;

// Helper type for raw rules
export type AppRawRule = RawRuleOf<AppAbility>;

// Represents the possible object types that can be passed as subjects for permission checks.
export type SubjectObject =
	| InferSelectModel<typeof ingestionSources>
	| InferSelectModel<typeof archivedEmails>
	| InferSelectModel<typeof users>
	| InferSelectModel<typeof roles>
	| AppSubjects;

// Function to create an ability instance from policies stored in the database
export function createAbilityFor(policies: CaslPolicy[]) {
	// We will not expand policies, if a role needs access to ingestion X and its archived emails, the policy should also grant access to archives belonging to ingestion X
	// const allPolicies = expandPolicies(policies);

	return createMongoAbility<AppAbility>(policies as AppRawRule[]);
}

/**
 * @deprecated This function should not be used since we don't need the inheritable behavior anymore.
 * Translates conditions on an 'ingestion' subject to equivalent conditions on an 'archive' subject.
 * This is used to implement inherent permissions, where permission on an ingestion source
 * implies permission on the emails it has ingested.
 * @param conditions The original conditions object for the 'ingestion' subject.
 * @returns A new conditions object for the 'archive' subject.
 */
function translateIngestionConditionsToArchive(
	conditions: Record<string, any>
): Record<string, any> {
	if (!conditions || typeof conditions !== 'object') {
		return conditions;
	}

	const translated: Record<string, any> = {};
	for (const key in conditions) {
		const value = conditions[key];

		// Handle logical operators recursively
		if (['$or', '$and', '$nor'].includes(key) && Array.isArray(value)) {
			translated[key] = value.map((v) => translateIngestionConditionsToArchive(v));
			continue;
		}
		if (key === '$not' && typeof value === 'object' && value !== null) {
			translated[key] = translateIngestionConditionsToArchive(value);
			continue;
		}

		// Translate field names
		let newKey = key;
		if (key === 'id') {
			newKey = 'ingestionSourceId';
		} else if (['userId', 'name', 'provider', 'status'].includes(key)) {
			newKey = `ingestionSource.${key}`;
		}

		translated[newKey] = value;
	}
	return translated;
}

/**
 * @deprecated This function should not be used since we don't need the inheritable behavior anymore.
 * Expands the given set of policies to include inherent permissions.
 * For example, a permission on an 'ingestion' source is expanded to grant
 * the same permission on 'archive' records related to that source.
 * @param policies The original array of CASL policies.
 * @returns A new array of policies including the expanded, inherent permissions.
 */
function expandPolicies(policies: CaslPolicy[]): CaslPolicy[] {
	const expandedPolicies: CaslPolicy[] = JSON.parse(JSON.stringify(policies));

	// Create a set of all actions that are already explicitly defined for the 'archive' subject.
	const existingArchiveActions = new Set<string>();
	policies.forEach((p) => {
		if (p.subject === 'archive') {
			const actions = Array.isArray(p.action) ? p.action : [p.action];
			actions.forEach((a) => existingArchiveActions.add(a));
		}
		// Only expand `can` rules for the 'ingestion' subject.
		if (p.subject === 'ingestion' && !p.inverted) {
			const policyActions = Array.isArray(p.action) ? p.action : [p.action];

			// Check if any action in the current ingestion policy already has an explicit archive policy.
			const hasExplicitArchiveRule = policyActions.some(
				(a) => existingArchiveActions.has(a) || existingArchiveActions.has('manage')
			);

			// If a more specific rule for 'archive' already exists, do not expand this ingestion rule,
			// as it would create a conflicting, overly permissive rule.
			if (hasExplicitArchiveRule) {
				return;
			}

			const archivePolicy: CaslPolicy = {
				...JSON.parse(JSON.stringify(p)),
				subject: 'archive',
			};
			if (p.conditions) {
				archivePolicy.conditions = translateIngestionConditionsToArchive(p.conditions);
			}
			expandedPolicies.push(archivePolicy);
		}
	});

	policies.forEach((policy) => {});

	return expandedPolicies;
}
