import { ok } from '@atcute/client';
import type { ResolvedActor } from '@atcute/identity-resolver';
import type { Did, Nsid, ResourceUri } from '@atcute/lexicons/syntax';
import type { OAuthUserAgent } from '@atcute/oauth-browser-client';
import { ShTangledGraphFollow } from '@atcute/tangled';

import { getRpc } from './client';
import { createAuthRpc, resolveActor, resolveActorForDisplay } from './identity';
import { parseAtUri, type RecordCreationResult } from './records';
import { listAllAppviewRecords } from './appview';

const FOLLOW_COLLECTION = 'sh.tangled.graph.follow' as Nsid;

export interface FollowRecord {
	uri: ResourceUri;
	cid: string;
	rkey: string;
	value: ShTangledGraphFollow.Main;
}

export const listFollowRecords = async (actor: string | ResolvedActor): Promise<FollowRecord[]> => {
	const resolved = typeof actor === 'string' ? await resolveActor(actor) : actor;
	const rpc = getRpc(resolved.pds);
	const records: FollowRecord[] = [];
	let cursor: string | undefined;

	do {
		const page = await ok(
			rpc.get('com.atproto.repo.listRecords', {
				params: {
					repo: resolved.did,
					collection: FOLLOW_COLLECTION,
					limit: 100,
					cursor,
					reverse: true,
				},
			}),
		);

		records.push(
			...page.records.map((record) => ({
				uri: record.uri,
				cid: record.cid,
				rkey: parseAtUri(record.uri).rkey,
				value: record.value as ShTangledGraphFollow.Main,
			})),
		);
		cursor = page.cursor;
	} while (cursor);

	return records;
};

export const createFollow = async (agent: OAuthUserAgent, followeeDid: Did): Promise<RecordCreationResult> => {
	const rpc = createAuthRpc(agent);
	const record: ShTangledGraphFollow.Main = {
		$type: 'sh.tangled.graph.follow',
		subject: followeeDid,
		createdAt: new Date().toISOString(),
	};
	const result = await ok(
		rpc.post('com.atproto.repo.createRecord', {
			input: {
				repo: agent.sub,
				collection: FOLLOW_COLLECTION,
				record,
			},
		}),
	);

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

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

export interface FollowerFollowingItem {
	did: Did;
	handle: string;
	actor: ResolvedActor;
	followRecord: FollowRecord;
}

export const getFollowers = async (did: Did): Promise<FollowerFollowingItem[]> => {
	const records = await listAllAppviewRecords<ShTangledGraphFollow.Main>('sh.tangled.graph.listFollows', did);
	return Promise.all(
		records.map(async (record) => {
			const parsed = parseAtUri(record.uri);
			const actor = await resolveActorForDisplay(parsed.did);
			return {
				did: parsed.did,
				handle: actor.handle,
				actor,
				followRecord: {
					uri: record.uri,
					cid: record.cid ?? '',
					rkey: parsed.rkey,
					value: record.value,
				},
			};
		})
	);
};

export const getFollowing = async (did: Did): Promise<FollowerFollowingItem[]> => {
	const records = await listAllAppviewRecords<ShTangledGraphFollow.Main>('sh.tangled.graph.listFollowsBy', did);
	return Promise.all(
		records.map(async (record) => {
			const targetDid = record.value.subject as Did;
			const actor = await resolveActorForDisplay(targetDid);
			const parsed = parseAtUri(record.uri);
			return {
				did: targetDid,
				handle: actor.handle,
				actor,
				followRecord: {
					uri: record.uri,
					cid: record.cid ?? '',
					rkey: parsed.rkey,
					value: record.value,
				},
			};
		})
	);
};

const VOUCH_COLLECTION = 'sh.tangled.graph.vouch';

export interface VouchRecord {
	uri: string;
	cid: string;
	value: {
		$type: 'sh.tangled.graph.vouch';
		createdAt: string;
		kind: 'vouch' | 'denounce';
		reason?: string;
		evidences?: string[];
	};
}

export const getVouchRecord = async (
	viewerDid: Did,
	subjectDid: Did
): Promise<VouchRecord | null> => {
	try {
		const resolvedViewer = await resolveActor(viewerDid);
		const rpc = getRpc(resolvedViewer.pds);
		const result = await ok(
			rpc.get('com.atproto.repo.getRecord', {
				params: {
					repo: resolvedViewer.did,
					collection: VOUCH_COLLECTION,
					rkey: subjectDid,
				},
			})
		);
		return result as unknown as VouchRecord;
	} catch (e) {
		return null;
	}
};

export const putVouchRecord = async (
	agent: OAuthUserAgent,
	subjectDid: Did,
	kind: 'vouch' | 'denounce',
	reason?: string,
	evidences?: string[]
): Promise<void> => {
	const rpc = createAuthRpc(agent);
	const record = {
		$type: VOUCH_COLLECTION,
		createdAt: new Date().toISOString(),
		kind,
		reason: reason || undefined,
		evidences: evidences || undefined,
	};
	await ok(
		rpc.post('com.atproto.repo.putRecord', {
			input: {
				repo: agent.sub,
				collection: VOUCH_COLLECTION,
				rkey: subjectDid,
				record,
			},
		})
	);
};

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

