/**
 * Printful v2 API client.
 *
 * Handles catalog lookup, mockup generation, and order submission.
 * API v2 docs: https://developers.printful.com/docs/v2-beta/
 * Rate limit: 120 req/60s (leaky bucket), lower for mockups.
 */

import type { PodOrder, PodRecipient, PodMockup, PodVariant } from "./types";

const BASE_URL = "https://api.printful.com/v2";

/** In-memory cache for catalog variants: product_id -> { variants, ts } */
const variantCache = new Map<number, { variants: PodVariant[]; ts: number }>();
const VARIANT_CACHE_TTL = 86_400_000; // 24 hours in ms

export class PrintfulClient {
	private apiToken: string;
	private storeId: string;
	private sandbox: boolean;
	readonly enabled: boolean;

	constructor() {
		this.apiToken = process.env.PRINTFUL_API_TOKEN || "";
		this.storeId = process.env.PRINTFUL_STORE_ID || "";
		this.sandbox = process.env.POD_SANDBOX_MODE !== "false";
		this.enabled = !!this.apiToken;
	}

	private get headers(): Record<string, string> {
		const h: Record<string, string> = {
			Authorization: `Bearer ${this.apiToken}`,
			"Content-Type": "application/json",
		};
		if (this.storeId) h["X-PF-Store-Id"] = this.storeId;
		return h;
	}

	// ── Catalog ──

	async getCatalogVariants(productId: number): Promise<PodVariant[]> {
		const cached = variantCache.get(productId);
		if (cached && Date.now() - cached.ts < VARIANT_CACHE_TTL) {
			return cached.variants;
		}

		const resp = await fetch(
			`${BASE_URL}/catalog-products/${productId}/catalog-variants`,
			{ headers: this.headers },
		);
		if (!resp.ok) throw new Error(`Printful catalog error: ${resp.status}`);

		const data = (await resp.json()) as { data?: any[] };
		const raw = data.data || [];

		const variants: PodVariant[] = raw.map((v: any) => ({
			id: v.id,
			size: v.size || "",
			color: v.color || "",
			colorCode: v.color_code || "",
		}));

		variantCache.set(productId, { variants, ts: Date.now() });
		return variants;
	}

	/**
	 * Resolve (product_id, size, color) -> Printful catalog_variant_id.
	 * Our metadata uses SKU "71" + variants ["S","M","L",...].
	 * Printful orders require numeric catalog_variant_id.
	 */
	async resolveVariantId(
		productId: number,
		size: string,
		color = "Black",
	): Promise<number | null> {
		const variants = await this.getCatalogVariants(productId);

		// Exact match on size + color
		for (const v of variants) {
			if (
				v.size.toUpperCase() === size.toUpperCase() &&
				v.color.toLowerCase().includes(color.toLowerCase())
			) {
				return v.id;
			}
		}

		// Fallback: match size only
		for (const v of variants) {
			if (v.size.toUpperCase() === size.toUpperCase()) {
				return v.id;
			}
		}

		return null;
	}

	// ── Mockup Generation ──

	/**
	 * Start async mockup generation task (v2 format).
	 * Returns task_id to poll with getMockupTask().
	 */
	async createMockupTask(
		productId: number,
		variantIds: number[],
		imageUrl: string,
		placement = "front",
		technique = "dtg",
	): Promise<string> {
		const payload = {
			products: [
				{
					source: "catalog",
					catalog_product_id: productId,
					catalog_variant_ids: variantIds,
					placements: [
						{
							placement,
							technique,
							layers: [{ type: "file", url: imageUrl }],
						},
					],
				},
			],
			format: "png",
		};

		const resp = await fetch(`${BASE_URL}/mockup-tasks`, {
			method: "POST",
			headers: this.headers,
			body: JSON.stringify(payload),
		});
		if (!resp.ok) throw new Error(`Printful mockup task error: ${resp.status}`);

		const result = (await resp.json()) as { data?: any };
		const rawData = result.data;
		const data = Array.isArray(rawData) && rawData.length ? rawData[0] : rawData;
		const taskId = data?.id || data?.task_key || data?.task_id;
		return String(taskId);
	}

	/** Poll mockup task status. */
	async getMockupTask(taskId: string): Promise<{
		status: string;
		mockups?: PodMockup[];
		failureReasons?: string[];
	}> {
		const resp = await fetch(
			`${BASE_URL}/mockup-tasks?id=${encodeURIComponent(taskId)}`,
			{ headers: this.headers },
		);
		if (!resp.ok) throw new Error(`Printful mockup poll error: ${resp.status}`);

		const result = (await resp.json()) as { data?: any };
		const raw = result.data;
		const data = Array.isArray(raw) && raw.length ? raw[0] : (typeof raw === "object" ? raw : {});

		const mockups: PodMockup[] = (
			data.mockups || data.catalog_variant_mockups || []
		).map((m: any) => ({
			variantId: m.catalog_variant_id || m.variant_id,
			mockupUrl: m.mockup_url || m.url || "",
			placement: m.placement || "front",
		}));

		return {
			status: data.status || "",
			mockups,
			failureReasons: data.failure_reasons,
		};
	}

	/**
	 * Create mockup task and poll until complete.
	 * Returns mockup list or null on failure/timeout.
	 */
	async generateMockupAndWait(
		productId: number,
		variantIds: number[],
		imageUrl: string,
		placement = "front",
		technique = "dtg",
		maxPolls = 20,
		pollIntervalMs = 3000,
	): Promise<PodMockup[] | null> {
		const taskId = await this.createMockupTask(
			productId, variantIds, imageUrl, placement, technique,
		);

		for (let i = 0; i < maxPolls; i++) {
			await new Promise((r) => setTimeout(r, pollIntervalMs));
			const result = await this.getMockupTask(taskId);

			if (result.status === "completed") {
				return result.mockups || [];
			}
			if (result.status === "failed") {
				console.error(`Printful mockup task ${taskId} failed:`, result.failureReasons);
				return null;
			}
		}

		console.warn(`Printful mockup task ${taskId} timed out after ${maxPolls} polls`);
		return null;
	}

	// ── Orders ──

	async createOrder(
		items: {
			catalogVariantId: number;
			quantity: number;
			imageUrl: string;
			placement?: string;
		}[],
		recipient: PodRecipient,
	): Promise<PodOrder> {
		if (!this.enabled) throw new Error("Printful API token not configured");

		const orderItems = items.map((item) => ({
			source: "catalog",
			catalog_variant_id: item.catalogVariantId,
			quantity: item.quantity,
			placements: [
				{
					placement: item.placement || "front",
					technique: "dtg",
					layers: [{ type: "file", url: item.imageUrl }],
				},
			],
		}));

		const payload: any = {
			recipient: {
				name: recipient.name,
				address1: recipient.address1,
				...(recipient.address2 ? { address2: recipient.address2 } : {}),
				city: recipient.city,
				...(recipient.stateCode ? { state_code: recipient.stateCode } : {}),
				country_code: recipient.countryCode,
				zip: recipient.zip,
				...(recipient.email ? { email: recipient.email } : {}),
			},
			items: orderItems,
		};

		if (this.sandbox) payload.draft = true;

		const resp = await fetch(`${BASE_URL}/orders`, {
			method: "POST",
			headers: this.headers,
			body: JSON.stringify(payload),
		});
		if (!resp.ok) {
			const text = await resp.text();
			throw new Error(`Printful order error ${resp.status}: ${text.slice(0, 500)}`);
		}

		const result = (await resp.json()) as { data?: any };
		const data = result.data || {};

		return {
			id: String(data.id),
			provider: "printful",
			status: data.status || "draft",
			items: items.map((i) => ({
				sku: String(i.catalogVariantId),
				quantity: i.quantity,
				imageUrl: i.imageUrl,
				placement: i.placement || "front",
			})),
			recipient,
			createdAt: new Date().toISOString(),
			raw: data,
		};
	}

	async getOrder(orderId: string): Promise<PodOrder> {
		const resp = await fetch(`${BASE_URL}/orders/${orderId}`, {
			headers: this.headers,
		});
		if (!resp.ok) throw new Error(`Printful get order error: ${resp.status}`);

		const result = (await resp.json()) as { data?: any };
		const data = result.data || {};

		return {
			id: String(data.id),
			provider: "printful",
			status: data.status || "unknown",
			items: [],
			recipient: { name: "", address1: "", city: "", countryCode: "", zip: "" },
			createdAt: data.created_at || new Date().toISOString(),
			raw: data,
		};
	}
}
