import credentials from "../../../config/credentials.json";

/**
 * 瀏覽器端 Google Sheets 讀取服務
 * 用 service account 憑證（config/credentials.json）以 WebCrypto 簽 RS256 JWT 換 access token，
 * 直接呼叫 Sheets API 讀「專案_架構 Reference Guide」試算表。
 * 資料結構參考 quickopen（V:\php\quickopen）：
 * - 平台清單：「專案清單」分頁 A欄=專案名稱、B欄=遊戲名稱（含「停運」者略過）
 * - 各平台分頁：B/C/D 欄對應 Dev/B2B/Out 環境，列標籤含 WebSocket、後台資源、Web Patch、LiffID進遊戲
 */

const SPREADSHEET_ID: string = "1CdnUaQOs-iWlDE6axgdssyAXRxhkZp9xJ1CVq5AQ5u8";
const SCOPE: string = "https://www.googleapis.com/auth/spreadsheets.readonly";
const PROJECT_LIST_SHEET: string = "專案清單";

export type EnvName = "Dev" | "B2B" | "Out";

/** 環境對應欄位 index（A=0） */
const ENV_COL: Record<EnvName, number> = {
	Dev: 1,
	B2B: 2,
	Out: 3,
};

export interface PlatformOption {
	/** 代號：LP1、LC1、MP1... */
	code: string;
	/** 專案名稱：Line_Project_1 */
	project: string;
	/** 遊戲名稱：來博娛樂城 */
	gameName: string;
	/** 顯示文字：LP1 來博娛樂城 */
	label: string;
}

export interface PlatformEnvConfig {
	/** 連線主機（含 protocol，不含 port）例：https://game-b2b.lybobet.com */
	host: string;
	/** 連線埠 */
	port: number;
	/** 靜態資源網址（尾帶 /）例：https://static-b2b.lybobet.com/ */
	downloadUrl: string;
	/** 資源伺服器網址（尾帶 /） */
	patch: string;
	/** Liff ID（Dev 環境無 liff 網址時為 undefined） */
	liffId?: string;
	/** Line ID（去除 @） */
	lineId?: string;
	/** 來源分頁名稱 */
	sheetTitle: string;
}

// =======================================================================================
// Token

let _tokenCache: { token: string; exp: number } = null;

function base64urlOfBytes(bytes: Uint8Array): string {
	let bin: string = "";
	for (let i: number = 0; i < bytes.length; i++) {
		bin += String.fromCharCode(bytes[i]);
	}
	return btoa(bin).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
}

function base64urlOfJson(obj: object): string {
	return base64urlOfBytes(new TextEncoder().encode(JSON.stringify(obj)));
}

function pemToArrayBuffer(pem: string): ArrayBuffer {
	const b64: string = pem.replace(/-----[^-]+-----/g, "").replace(/\s+/g, "");
	const bin: string = atob(b64);
	const buf: ArrayBuffer = new ArrayBuffer(bin.length);
	const view: Uint8Array = new Uint8Array(buf);
	for (let i: number = 0; i < bin.length; i++) {
		view[i] = bin.charCodeAt(i);
	}
	return buf;
}

/**
 * 簽 RS256 JWT
 * 優先用 WebCrypto；非安全來源（例如用區網 IP http 開啟）沒有 crypto.subtle，改用 jsrsasign 純 JS 簽章
 */
async function signJwt(header: object, claim: object): Promise<string> {
	if (typeof crypto !== "undefined" && crypto.subtle) {
		const signInput: string = `${base64urlOfJson(header)}.${base64urlOfJson(claim)}`;
		const key: CryptoKey = await crypto.subtle.importKey(
			"pkcs8",
			pemToArrayBuffer(credentials.private_key),
			{ name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" },
			false,
			["sign"]
		);
		const sig: ArrayBuffer = await crypto.subtle.sign("RSASSA-PKCS1-v1_5", key, new TextEncoder().encode(signInput));
		return `${signInput}.${base64urlOfBytes(new Uint8Array(sig))}`;
	}
	const { KJUR } = await import("jsrsasign");
	return KJUR.jws.JWS.sign("RS256", JSON.stringify(header), JSON.stringify(claim), credentials.private_key);
}

/** 取得（快取的）access token */
async function getAccessToken(): Promise<string> {
	const now: number = Math.floor(Date.now() / 1000);
	if (_tokenCache && _tokenCache.exp - 60 > now) {
		return _tokenCache.token;
	}
	const header: object = { alg: "RS256", typ: "JWT" };
	const claim: object = {
		iss: credentials.client_email,
		scope: SCOPE,
		aud: credentials.token_uri,
		iat: now,
		exp: now + 3600,
	};
	const jwt: string = await signJwt(header, claim);

	const resp: Response = await fetch(credentials.token_uri, {
		method: "POST",
		headers: { "Content-Type": "application/x-www-form-urlencoded" },
		body: `grant_type=${encodeURIComponent("urn:ietf:params:oauth:grant-type:jwt-bearer")}&assertion=${jwt}`,
	});
	if (!resp.ok) {
		throw new Error(`Google 授權失敗 (${resp.status})`);
	}
	const data: any = await resp.json();
	_tokenCache = { token: data.access_token, exp: now + (+data.expires_in || 3600) };
	return _tokenCache.token;
}

// =======================================================================================
// Sheets API

async function sheetGet(path: string, params: Record<string, string> = {}): Promise<any> {
	const token: string = await getAccessToken();
	const qs: string = new URLSearchParams(params).toString();
	const resp: Response = await fetch(
		`https://sheets.googleapis.com/v4/spreadsheets/${SPREADSHEET_ID}${path}${qs ? `?${qs}` : ""}`,
		{ headers: { Authorization: `Bearer ${token}` } }
	);
	if (!resp.ok) {
		throw new Error(`讀取試算表失敗 (${resp.status})`);
	}
	return resp.json();
}

async function getValues(range: string): Promise<string[][]> {
	const data: any = await sheetGet(`/values/${encodeURIComponent(range)}`);
	return data.values ?? [];
}

let _sheetTitles: string[] = null;

async function getSheetTitles(): Promise<string[]> {
	if (_sheetTitles) {
		return _sheetTitles;
	}
	const data: any = await sheetGet("", { fields: "sheets.properties.title" });
	_sheetTitles = data.sheets.map((s: any) => s.properties.title);
	return _sheetTitles;
}

// =======================================================================================
// 業務邏輯

/** 由專案名稱推代號：Line_Project_1→LP1、Line_Cash_1→LC1、Super_Diamond_2→SD2 */
function projectToCode(project: string): string {
	const parts: string[] = project.split("_");
	if (parts.length === 1) {
		return project;
	}
	let code: string = "";
	for (const p of parts) {
		if (/^\d+$/.test(p)) {
			code += p;
		} else {
			code += p.charAt(0).toUpperCase();
		}
	}
	return code;
}

/** 讀「專案清單」取得平台選項（同 quickopen：略過停運與空列） */
export async function fetchPlatformList(): Promise<PlatformOption[]> {
	const rows: string[][] = await getValues(`${PROJECT_LIST_SHEET}!A4:B`);
	const options: PlatformOption[] = [];
	let emptyCount: number = 0;
	for (const row of rows) {
		const project: string = (row[0] ?? "").trim();
		const gameName: string = (row[1] ?? "").trim();
		if (!project && !gameName) {
			emptyCount++;
			if (emptyCount >= 3) {
				break;
			}
			continue;
		}
		emptyCount = 0;
		if (!project || !gameName) {
			continue;
		}
		if (gameName.includes("停運")) {
			continue;
		}
		const code: string = projectToCode(project);
		options.push({
			code,
			project,
			gameName,
			label: `${code} ${gameName}`,
		});
	}
	return options;
}

/** 找平台對應的分頁：優先找標題含代號（LP1），其次含遊戲名稱前 2 字（同 quickopen 規則） */
async function findSheetTitle(platform: PlatformOption): Promise<string> {
	const titles: string[] = await getSheetTitles();
	let title: string = titles.find((t: string) => t.includes(platform.code));
	if (!title) {
		const prefix: string = platform.gameName.substring(0, 2);
		title = titles.find((t: string) => t.includes(prefix));
	}
	if (!title) {
		throw new Error(`找不到 ${platform.label} 對應的工作表`);
	}
	return title;
}

function rowByLabel(rows: string[][], label: string): string[] {
	return rows.find((r: string[]) => (r[0] ?? "").trim().startsWith(label));
}

function ensureSlash(url: string): string {
	return url.endsWith("/") ? url : `${url}/`;
}

/** 讀平台分頁全部列 */
async function getPlatformRows(platform: PlatformOption): Promise<{ sheetTitle: string; rows: string[][] }> {
	const sheetTitle: string = await findSheetTitle(platform);
	const rows: string[][] = await getValues(`${sheetTitle}!A1:E20`);
	return { sheetTitle, rows };
}

/** 讀「跳過維護阻擋(ver2.0 use)」列取得指定環境的遊戲網址 */
export async function fetchGameUrl(platform: PlatformOption, env: EnvName): Promise<string> {
	const { sheetTitle, rows } = await getPlatformRows(platform);
	const url: string = (rowByLabel(rows, "跳過維護阻擋")?.[ENV_COL[env]] ?? "").trim();
	if (!/^https?:\/\//.test(url)) {
		throw new Error(`${sheetTitle} 的 ${env} 環境沒有有效的遊戲網址`);
	}
	return url;
}

/** 讀平台分頁，解析指定環境的連線設定 */
export async function fetchPlatformConfig(platform: PlatformOption, env: EnvName): Promise<PlatformEnvConfig> {
	const { sheetTitle, rows } = await getPlatformRows(platform);
	const col: number = ENV_COL[env];

	const wsRow: string[] = rowByLabel(rows, "WebSocket");
	const wsUrl: string = (wsRow?.[col] ?? "").trim();
	if (!wsUrl || wsUrl === "X" || !/^https?:\/\//.test(wsUrl)) {
		throw new Error(`${sheetTitle} 的 ${env} 環境沒有有效的 WebSocket 位址`);
	}
	const url: URL = new URL(wsUrl);
	const host: string = `${url.protocol}//${url.hostname}`;
	const port: number = +url.port || 9005;

	const dlUrl: string = (rowByLabel(rows, "後台資源")?.[col] ?? "").trim();
	if (!dlUrl || !/^https?:\/\//.test(dlUrl)) {
		throw new Error(`${sheetTitle} 的 ${env} 環境沒有有效的後台資源位址`);
	}

	const patchUrl: string = (rowByLabel(rows, "Web Patch")?.[col] ?? "").trim();

	const liffUrl: string = (rowByLabel(rows, "LiffID進遊戲")?.[col] ?? "").trim();
	const liffMatch: RegExpMatchArray = liffUrl.match(/liff\.line\.me\/([\w-]+)/);

	const lineRaw: string = (rowByLabel(rows, "Messaging API LineID")?.[col] ?? "").trim();
	const lineMatch: RegExpMatchArray = lineRaw.match(/@([\w-]+)/);

	return {
		host,
		port,
		downloadUrl: ensureSlash(dlUrl),
		patch: /^https?:\/\//.test(patchUrl) ? ensureSlash(patchUrl) : undefined,
		liffId: liffMatch ? liffMatch[1] : undefined,
		lineId: lineMatch ? lineMatch[1] : undefined,
		sheetTitle,
	};
}
