import { microhash } from '@gitlens/utils/hash.js';
import type { IntegrationIds, SupportedCloudIntegrationIds } from '../constants.js';
import {
	GitCloudHostIntegrationId,
	GitSelfManagedHostIntegrationId,
	IssuesCloudHostIntegrationId,
	supportedOrderedCloudIntegrationIds,
} from '../constants.js';

/**
 * Mirrors the shape of `vscode.AuthenticationSession` so the integrations
 * package can stay vscode-free. Host adapters bridge VS Code sessions to
 * this shape via {@link AuthenticationProvider}.
 */
export interface AuthenticationSessionLike {
	readonly id: string;
	readonly accessToken: string;
	readonly account: { readonly id: string; readonly label: string };
	readonly scopes: readonly string[];
}

export interface ProviderAuthenticationSession extends AuthenticationSessionLike {
	readonly cloud: boolean;
	readonly type: CloudIntegrationAuthType | undefined;
	readonly expiresAt?: Date;
	readonly domain: string;
	readonly protocol?: string;
}

export interface TokenInfo<T extends IntegrationIds | 'gitkraken' = IntegrationIds | 'gitkraken'> {
	readonly providerId: T;
	/**
	 * The first 3 characters of the md5 hash of the token.
	 * It's a very obfuscated representation of the token that we can use in logs
	 * to see whether the token survives refreshing or gets updated.
	 */
	readonly microHash: string | undefined;
	readonly cloud: boolean;
	readonly type: CloudIntegrationAuthType | undefined;
	readonly scopes: readonly string[] | undefined;
	readonly expiresAt?: Date;
}

export interface TokenWithInfo<T extends IntegrationIds = IntegrationIds> extends TokenInfo<T> {
	readonly accessToken: string;
}

export function toTokenInfo<T extends IntegrationIds | 'gitkraken'>(
	providerId: T,
	accessToken: string | undefined,
	info: { cloud: boolean; type: CloudIntegrationAuthType | undefined; scopes?: readonly string[]; expiresAt?: Date },
): TokenInfo<T> {
	return {
		providerId: providerId,
		microHash: microhash(accessToken, 3),
		cloud: info.cloud,
		type: info.type,
		scopes: info.scopes,
		expiresAt: info.expiresAt,
	};
}

export function toTokenWithInfo<T extends IntegrationIds>(
	providerId: T,
	session: ProviderAuthenticationSession,
	altToken?: string,
): TokenWithInfo<T> {
	const { accessToken: sessionToken, ...sessionInfo } = session;
	const accessToken = altToken ?? session.accessToken;
	return {
		// pass original token info to form the correlated microhash
		...toTokenInfo(providerId, sessionToken, sessionInfo),
		// use the actual token used for the request
		accessToken: accessToken,
	};
}

export type TokenOptInfo<T extends IntegrationIds = IntegrationIds> =
	| TokenWithInfo<T>
	| { providerId: T; accessToken?: undefined };

export interface ConfiguredIntegrationDescriptor {
	/**
	 * Stable per-connection identifier, distinct from the domain, so multiple accounts can be
	 * connected to the same provider+domain simultaneously. For legacy/migrated single connections
	 * this is the domain (self-managed) or the provider's canonical domain (cloud), which keeps the
	 * existing secret key (`integration.auth[.cloud]:{id}|{domain}`) valid with zero migration.
	 */
	readonly id: string;
	/**
	 * Whether this is the connection used by default for the provider. Optional: when no descriptor
	 * for a provider+domain carries an explicit flag, the first one is treated as primary. Only
	 * {@link ConfiguredIntegrationService.setPrimaryConnection} sets the flag (and clears it on siblings).
	 */
	readonly primary?: boolean;
	/** The connection's auth type (`oauth`/`pat`), when known. */
	readonly type?: CloudIntegrationAuthType;
	/**
	 * Human-readable account handle for this connection (e.g. the GitHub login). Resolved from the
	 * provider API (the token backend doesn't expose it); absent until resolved or if the lookup fails.
	 */
	readonly accountName?: string;
	readonly cloud: boolean;
	readonly integrationId: IntegrationIds;
	readonly scopes: string;
	readonly domain?: string;
	readonly expiresAt?: string | Date;
}

export interface CloudIntegrationAuthenticationSession {
	type: CloudIntegrationAuthType;
	accessToken: string;
	domain: string;
	expiresIn: number;
	scopes: string;
	/** Backend per-connection token identifier, when the provider-tokens backend supports multi-account. */
	id?: string;
}

export interface CloudIntegrationAuthorization {
	url: string;
}

export interface CloudIntegrationConnection {
	type: CloudIntegrationAuthType;
	provider: CloudIntegrationType;
	domain: string;
	/** Backend per-connection token identifier, when the provider-tokens backend supports multi-account. */
	id?: string;
	/** Whether the backend marks this connection as the primary/default for the provider. */
	primary?: boolean;
	/** Human-readable account handle, when the backend provides it on the connection (may be absent). */
	accountName?: string;
}

export type CloudIntegrationType =
	| 'jira'
	| 'linear'
	| 'trello'
	| 'gitlab'
	| 'github'
	| 'bitbucket'
	| 'bitbucketServer'
	| 'azure'
	| 'azureDevopsServer'
	| 'githubEnterprise'
	| 'gitlabSelfHosted';

export type CloudIntegrationAuthType = 'oauth' | 'pat';

export const CloudIntegrationAuthenticationUriPathPrefix = 'did-authenticate-cloud-integration';

/** Returns all supported cloud integration ids. */
export function getSupportedCloudIntegrationIds(): readonly SupportedCloudIntegrationIds[] {
	// Return the shared const directly (readonly so callers can't mutate it) — this runs on hot,
	// per-integration membership paths, so avoid allocating a fresh array per call.
	return supportedOrderedCloudIntegrationIds;
}

/** Whether `id` is a supported cloud integration id. */
export function isSupportedCloudIntegrationId(id: string): id is SupportedCloudIntegrationIds {
	return getSupportedCloudIntegrationIds().includes(id as SupportedCloudIntegrationIds);
}

export function isIssueCloudIntegrationId(id: string): id is IssuesCloudHostIntegrationId {
	const issueIds: string[] = Object.values(IssuesCloudHostIntegrationId);
	return issueIds.includes(id);
}

export const toIntegrationId: { [key in CloudIntegrationType]: IntegrationIds } = {
	jira: IssuesCloudHostIntegrationId.Jira,
	linear: IssuesCloudHostIntegrationId.Linear,
	trello: IssuesCloudHostIntegrationId.Trello,
	gitlab: GitCloudHostIntegrationId.GitLab,
	github: GitCloudHostIntegrationId.GitHub,
	githubEnterprise: GitSelfManagedHostIntegrationId.CloudGitHubEnterprise,
	gitlabSelfHosted: GitSelfManagedHostIntegrationId.CloudGitLabSelfHosted,
	bitbucket: GitCloudHostIntegrationId.Bitbucket,
	bitbucketServer: GitSelfManagedHostIntegrationId.BitbucketServer,
	azure: GitCloudHostIntegrationId.AzureDevOps,
	azureDevopsServer: GitSelfManagedHostIntegrationId.AzureDevOpsServer,
};

export const toCloudIntegrationType: { [key in IntegrationIds]: CloudIntegrationType | undefined } = {
	[IssuesCloudHostIntegrationId.Jira]: 'jira',
	[IssuesCloudHostIntegrationId.Linear]: 'linear',
	[IssuesCloudHostIntegrationId.Trello]: 'trello',
	[GitCloudHostIntegrationId.GitLab]: 'gitlab',
	[GitCloudHostIntegrationId.GitHub]: 'github',
	[GitCloudHostIntegrationId.Bitbucket]: 'bitbucket',
	[GitCloudHostIntegrationId.AzureDevOps]: 'azure',
	[GitSelfManagedHostIntegrationId.AzureDevOpsServer]: 'azureDevopsServer',
	[GitSelfManagedHostIntegrationId.CloudGitHubEnterprise]: 'githubEnterprise',
	[GitSelfManagedHostIntegrationId.CloudGitLabSelfHosted]: 'gitlabSelfHosted',
	[GitSelfManagedHostIntegrationId.BitbucketServer]: 'bitbucketServer',
};
