import { account, invitation, member, organization, ssoProvider, user } from "../../auth/auth-schema.js";
import { parseAllowedAuthMethods } from "../../auth/auth-types.js";
import { and, desc, eq, inArray, or } from "drizzle-orm";
import { generateId } from "better-auth";

//#region src/data-access/runtime/organizations.ts
/**
* Organization and Member data access layer
* All database queries for Better Auth's organization/member tables
*/
/**
* Get all organizations for a user
* Queries Better Auth's member and organization tables
* Returns Date for createdAt (converted to string at API boundary)
*/
const getUserOrganizationsFromDb = (db) => async (userId) => {
	return (await db.select({
		id: member.id,
		userId: member.userId,
		organizationId: member.organizationId,
		role: member.role,
		createdAt: member.createdAt,
		organizationName: organization.name,
		organizationSlug: organization.slug
	}).from(member).leftJoin(organization, eq(member.organizationId, organization.id)).where(eq(member.userId, userId)).orderBy(desc(member.createdAt))).map((row) => ({
		...row,
		createdAt: new Date(row.createdAt)
	}));
};
/**
* Get pending invitations for a user by email
* Returns invitations with status 'pending' that haven't expired
*/
const getPendingInvitationsByEmail = (db) => async (email) => {
	const now = /* @__PURE__ */ new Date();
	return (await db.select({
		id: invitation.id,
		email: invitation.email,
		organizationId: invitation.organizationId,
		organizationName: organization.name,
		organizationSlug: organization.slug,
		role: invitation.role,
		status: invitation.status,
		expiresAt: invitation.expiresAt,
		inviterId: invitation.inviterId,
		authMethod: invitation.authMethod
	}).from(invitation).leftJoin(organization, eq(invitation.organizationId, organization.id)).where(and(eq(invitation.email, email), eq(invitation.status, "pending")))).filter((inv) => new Date(inv.expiresAt) > now);
};
/**
* Add user to organization
* Directly inserts into Better Auth's member table
*/
const addUserToOrganization = (db) => async (data) => {
	if ((await db.select().from(organization).where(eq(organization.id, data.organizationId)).limit(1)).length === 0) throw new Error(`Organization ${data.organizationId} does not exist`);
	if ((await db.select().from(member).where(and(eq(member.userId, data.userId), eq(member.organizationId, data.organizationId))).limit(1)).length > 0) {
		if (data.isServiceAccount) await db.update(organization).set({ serviceAccountUserId: data.userId }).where(eq(organization.id, data.organizationId));
		return;
	}
	await db.insert(member).values({
		id: `${data.userId}_${data.organizationId}`,
		userId: data.userId,
		organizationId: data.organizationId,
		role: data.role,
		createdAt: /* @__PURE__ */ new Date()
	});
	if (data.isServiceAccount) await db.update(organization).set({ serviceAccountUserId: data.userId }).where(eq(organization.id, data.organizationId));
};
const upsertOrganization = (db) => async (data) => {
	if ((await db.select().from(organization).where(or(eq(organization.id, data.organizationId), eq(organization.slug, data.slug))).limit(1)).length > 0) return { created: false };
	await db.insert(organization).values({
		id: data.organizationId,
		name: data.name,
		slug: data.slug,
		createdAt: /* @__PURE__ */ new Date(),
		logo: data.logo ?? null,
		metadata: data.metadata ?? null
	});
	return { created: true };
};
/**
* Get authentication providers for a list of users WITHIN an organization.
* Returns which providers each member has linked (e.g., 'credential', 'google').
* Results are scoped to members of `organizationId`; users outside it return no providers.
*/
const getUserProvidersFromDb = (db) => async (userIds, organizationId) => {
	if (userIds.length === 0) return [];
	const accounts = await db.select({
		userId: account.userId,
		providerId: account.providerId
	}).from(account).innerJoin(member, and(eq(member.userId, account.userId), eq(member.organizationId, organizationId))).where(inArray(account.userId, userIds));
	const providerMap = /* @__PURE__ */ new Map();
	for (const acc of accounts) {
		const existing = providerMap.get(acc.userId) || [];
		if (!existing.includes(acc.providerId)) existing.push(acc.providerId);
		providerMap.set(acc.userId, existing);
	}
	return userIds.map((userId) => ({
		userId,
		providers: providerMap.get(userId) || []
	}));
};
const getAllowedAuthMethods = (db) => async (organizationId) => {
	const org = (await db.select({ allowedAuthMethods: organization.allowedAuthMethods }).from(organization).where(eq(organization.id, organizationId)).limit(1))[0];
	if (!org) return [{ method: "email-password" }];
	return parseAllowedAuthMethods(org.allowedAuthMethods);
};
/**
* Create an invitation directly in db.
* Accepts an optional explicit authMethod; defaults to email-password.
*/
const createInvitationInDb = (db) => async (data) => {
	const orgSettings = (await db.select({
		serviceAccountUserId: organization.serviceAccountUserId,
		allowedAuthMethods: organization.allowedAuthMethods,
		preferredAuthMethod: organization.preferredAuthMethod
	}).from(organization).where(eq(organization.id, data.organizationId)).limit(1))[0];
	if (!orgSettings?.serviceAccountUserId) throw new Error(`Organization ${data.organizationId} does not have a serviceAccountUserId configured`);
	const resolvedMethod = data.authMethod || orgSettings.preferredAuthMethod || "email-password";
	const inviteId = generateId();
	const expiresAt = new Date(Date.now() + 3600 * 1e3);
	await db.insert(invitation).values({
		id: inviteId,
		organizationId: data.organizationId,
		email: data.email,
		role: "member",
		status: "pending",
		expiresAt,
		inviterId: orgSettings.serviceAccountUserId,
		authMethod: resolvedMethod
	});
	return {
		id: inviteId,
		authMethod: resolvedMethod
	};
};
const getSSOProvidersByDomain = (db) => async (domain) => {
	return (await db.select({
		providerId: ssoProvider.providerId,
		issuer: ssoProvider.issuer,
		domain: ssoProvider.domain,
		organizationId: ssoProvider.organizationId,
		oidcConfig: ssoProvider.oidcConfig,
		samlConfig: ssoProvider.samlConfig
	}).from(ssoProvider).where(eq(ssoProvider.domain, domain))).map((provider) => ({
		providerId: provider.providerId,
		issuer: provider.issuer,
		domain: provider.domain,
		organizationId: provider.organizationId,
		providerType: provider.samlConfig ? "saml" : "oidc"
	}));
};
/**
* Filters org-allowed auth methods by email domain.
* SSO providers are only included if their domain matches the user's email domain.
* Non-SSO methods (email-password, google) pass through unfiltered.
*/
const getFilteredAuthMethodsForEmail = (db) => async (organizationId, email) => {
	const emailDomain = email.split("@")[1]?.toLowerCase();
	if (!emailDomain) return [];
	const [allowed, domainProviders] = await Promise.all([getAllowedAuthMethods(db)(organizationId), getSSOProvidersByDomain(db)(emailDomain)]);
	return allowedMethodsToMethodOptions(allowed, domainProviders.filter((p) => p.organizationId === organizationId));
};
function allowedMethodsToMethodOptions(methods, ssoProviders) {
	const options = [];
	for (const m of methods) if (m.method === "email-password") options.push({ method: "email-password" });
	else if (m.method === "google") options.push({ method: "google" });
	else if (m.method === "microsoft") options.push({ method: "microsoft" });
	else if (m.method === "sso") {
		if (!m.enabled) continue;
		const provider = ssoProviders.find((p) => p.providerId === m.providerId);
		if (!provider) continue;
		options.push({
			method: "sso",
			providerId: m.providerId,
			providerType: provider.providerType,
			displayName: m.displayName
		});
	}
	return options;
}
/**
* Main auth-lookup query for the login flow.
* Returns org-grouped methods based on SSO domain match and/or user org membership.
*/
const getAuthLookupForEmail = (db) => async (email) => {
	const emailDomain = email.split("@")[1]?.toLowerCase();
	if (!emailDomain) return [];
	const orgMap = /* @__PURE__ */ new Map();
	const domainProviders = await getSSOProvidersByDomain(db)(emailDomain);
	const orgIdsFromSSO = [...new Set(domainProviders.map((p) => p.organizationId).filter(Boolean))];
	for (const orgId of orgIdsFromSSO) {
		const org = (await db.select({
			id: organization.id,
			name: organization.name,
			slug: organization.slug,
			allowedAuthMethods: organization.allowedAuthMethods,
			preferredAuthMethod: organization.preferredAuthMethod
		}).from(organization).where(eq(organization.id, orgId)).limit(1))[0];
		if (!org) continue;
		const allowed = parseAllowedAuthMethods(org.allowedAuthMethods);
		const orgSSO = domainProviders.filter((p) => p.organizationId === orgId);
		orgMap.set(orgId, {
			organizationId: org.id,
			organizationName: org.name,
			organizationSlug: org.slug,
			methods: allowedMethodsToMethodOptions(allowed, orgSSO)
		});
	}
	const userRow = await db.select({ id: user.id }).from(user).where(eq(user.email, email.toLowerCase())).limit(1);
	if (userRow[0]) {
		const memberships = await db.select({
			organizationId: member.organizationId,
			orgName: organization.name,
			orgSlug: organization.slug,
			allowedAuthMethods: organization.allowedAuthMethods,
			preferredAuthMethod: organization.preferredAuthMethod
		}).from(member).innerJoin(organization, eq(member.organizationId, organization.id)).where(eq(member.userId, userRow[0].id));
		for (const m of memberships) {
			if (orgMap.has(m.organizationId)) continue;
			const allowed = parseAllowedAuthMethods(m.allowedAuthMethods);
			const orgSSO = domainProviders.filter((p) => p.organizationId === m.organizationId);
			orgMap.set(m.organizationId, {
				organizationId: m.organizationId,
				organizationName: m.orgName,
				organizationSlug: m.orgSlug,
				methods: allowedMethodsToMethodOptions(allowed, orgSSO)
			});
		}
		const serviceAccountOrgs = await db.select({
			id: organization.id,
			name: organization.name,
			slug: organization.slug
		}).from(organization).where(eq(organization.serviceAccountUserId, userRow[0].id));
		for (const org of serviceAccountOrgs) {
			const existing = orgMap.get(org.id);
			if (existing) {
				if (!existing.methods.some((m) => m.method === "email-password")) existing.methods.unshift({ method: "email-password" });
			} else orgMap.set(org.id, {
				organizationId: org.id,
				organizationName: org.name,
				organizationSlug: org.slug,
				methods: [{ method: "email-password" }]
			});
		}
	}
	return [...orgMap.values()];
};

//#endregion
export { addUserToOrganization, allowedMethodsToMethodOptions, createInvitationInDb, getAllowedAuthMethods, getAuthLookupForEmail, getFilteredAuthMethodsForEmail, getPendingInvitationsByEmail, getSSOProvidersByDomain, getUserOrganizationsFromDb, getUserProvidersFromDb, upsertOrganization };