import { ok } from '@atcute/client';
import type { Did } from '@atcute/lexicons/syntax';
import type { OAuthUserAgent } from '@atcute/oauth-browser-client';
import { ShTangledFeedStar } from '@atcute/tangled';

import {
	appviewFallback,
	getDistinctAppviewRecordCount,
	listAllAppviewRecords,
	type AppviewRecordView,
} from './appview';
import {
	findBacklinkRefByDid,
	getBacklinkDids,
} from './backlinks';
import { STAR_COLLECTION } from './constants';
import { createAuthRpc } from './identity';
import {
	parseAtUri,
	type RecordCreationResult,
} from './records';
import type { RepoContext } from './repos';

export interface RepoStarSummary {
	count: number;
	isStarred: boolean;
	currentUserStarRkey?: string;
}

const STAR_BACKLINK_SOURCES = [
	'sh.tangled.feed.star:subject.did',
	'sh.tangled.feed.star:subjectDid',
];

const getRepoStarSummaryFromBacklinks = async (repo: RepoContext, viewerDid?: Did | null): Promise<RepoStarSummary> => {
	const starrers = new Set<Did>();
	const backlinkDidGroups = await Promise.all(STAR_BACKLINK_SOURCES.map((source) => getBacklinkDids(repo.repoDid, source)));

	for (const did of backlinkDidGroups.flat()) {
		starrers.add(did);
	}

	const currentUserStar =
		viewerDid && starrers.has(viewerDid)
			? await findBacklinkRefByDid(repo.repoDid, STAR_BACKLINK_SOURCES, viewerDid)
			: undefined;

	return {
		count: starrers.size,
		isStarred: !!currentUserStar,
		currentUserStarRkey: currentUserStar?.rkey,
	};
};

const getRepoStarCountFromBacklinks = async (repo: RepoContext): Promise<number> => {
	const summary = await getRepoStarSummaryFromBacklinks(repo);
	return summary.count;
};

const appviewStarSubjectDid = (star: ShTangledFeedStar.Main): Did | null => {
	const subject = star.subject as { did?: unknown; subjectDid?: unknown };
	const did = subject.did ?? subject.subjectDid;
	return typeof did === 'string' && did.startsWith('did:') ? (did as Did) : null;
};

const getRepoStarCountFromAppview = async (repo: RepoContext): Promise<number> =>
	getDistinctAppviewRecordCount('sh.tangled.feed.countStars', repo.repoDid);

const findCurrentUserStarFromAppview = async (
	repo: RepoContext,
	viewerDid: Did,
): Promise<AppviewRecordView<ShTangledFeedStar.Main> | undefined> => {
	const stars = await listAllAppviewRecords<ShTangledFeedStar.Main>('sh.tangled.feed.listStarsBy', viewerDid);
	return stars.find((star) => appviewStarSubjectDid(star.value) === repo.repoDid);
};

const getRepoStarSummaryFromAppview = async (
	repo: RepoContext,
	viewerDid?: Did | null,
): Promise<RepoStarSummary> => {
	const [count, currentUserStar] = await Promise.all([
		getRepoStarCountFromAppview(repo),
		viewerDid ? findCurrentUserStarFromAppview(repo, viewerDid) : undefined,
	]);

	return {
		count,
		isStarred: !!currentUserStar,
		currentUserStarRkey: currentUserStar ? parseAtUri(currentUserStar.uri).rkey : undefined,
	};
};

export const getRepoStarSummary = async (repo: RepoContext, viewerDid?: Did | null): Promise<RepoStarSummary> =>
	appviewFallback(
		() => getRepoStarSummaryFromAppview(repo, viewerDid),
		() => getRepoStarSummaryFromBacklinks(repo, viewerDid),
	);

export const getRepoStarCount = async (repo: RepoContext): Promise<number> =>
	appviewFallback(
		() => getRepoStarCountFromAppview(repo),
		() => getRepoStarCountFromBacklinks(repo),
	);

export const createRepoStar = async (agent: OAuthUserAgent, repo: RepoContext): Promise<RecordCreationResult> => {
	const rpc = createAuthRpc(agent);
	const record: ShTangledFeedStar.Main = {
		$type: 'sh.tangled.feed.star',
		subject: {
			$type: 'sh.tangled.feed.star#repo',
			did: repo.repoDid,
		},
		createdAt: new Date().toISOString(),
	};
	const result = await ok(
		rpc.post('com.atproto.repo.createRecord', {
			input: {
				repo: agent.sub,
				collection: STAR_COLLECTION,
				record,
			},
		}),
	);

	return {
		uri: result.uri,
		rkey: parseAtUri(result.uri).rkey,
	};
};

export const deleteRepoStar = async (agent: OAuthUserAgent, rkey: string): Promise<void> => {
	const rpc = createAuthRpc(agent);
	await ok(
		rpc.post('com.atproto.repo.deleteRecord', {
			input: {
				repo: agent.sub,
				collection: STAR_COLLECTION,
				rkey,
			},
		}),
	);
};
