/**
 * E2E tests for CLI commands against a real Astro dev server.
 *
 * Shells out to the actual `emdash` binary with --url and --token
 * flags, verifying real command output and exit codes.
 *
 * Runs by default. Requires built artifacts (auto-builds if missing).
 */

import { execFile } from "node:child_process";
import { resolve } from "node:path";
import { promisify } from "node:util";

import { describe, it, expect, beforeAll, afterAll } from "vitest";

import type { TestServerContext } from "../server.js";
import { assertNodeVersion, createTestServer } from "../server.js";

const exec = promisify(execFile);

const PORT = 4398; // Different port from client integration tests

// Path to the built CLI binary
const CLI_BIN = resolve(import.meta.dirname, "../../../dist/cli/index.mjs");

describe("CLI Integration", () => {
	let ctx: TestServerContext;

	beforeAll(async () => {
		assertNodeVersion();
		ctx = await createTestServer({ port: PORT });
	});

	afterAll(async () => {
		await ctx?.cleanup();
	});

	/** Run an emdash CLI command and return stdout */
	async function cli(...args: string[]): Promise<string> {
		const { stdout } = await exec(
			"node",
			[CLI_BIN, ...args, "--url", ctx.baseUrl, "--token", ctx.token, "--json"],
			{
				timeout: 15_000,
			},
		);
		return stdout;
	}

	/** Run CLI and parse JSON output */
	async function cliJson<T = unknown>(...args: string[]): Promise<T> {
		const stdout = await cli(...args);
		return JSON.parse(stdout) as T;
	}

	// -----------------------------------------------------------------------
	// Schema commands
	// -----------------------------------------------------------------------

	describe("schema", () => {
		it("lists collections", async () => {
			const result = await cliJson<{ slug: string }[]>("schema", "list");
			expect(Array.isArray(result)).toBe(true);
			const slugs = result.map((c) => c.slug);
			expect(slugs).toContain("posts");
			expect(slugs).toContain("pages");
		});

		it("gets a single collection", async () => {
			const result = await cliJson<{ slug: string; label: string }>("schema", "get", "posts");
			expect(result.slug).toBe("posts");
			expect(result.label).toBe("Posts");
		});

		it("creates and deletes a collection", async () => {
			const created = await cliJson<{ slug: string }>(
				"schema",
				"create",
				"cli_temp",
				"--label",
				"CLI Temp",
			);
			expect(created.slug).toBe("cli_temp");

			// Verify it exists
			const list = await cliJson<{ slug: string }[]>("schema", "list");
			expect(list.map((c) => c.slug)).toContain("cli_temp");

			// Delete
			await cli("schema", "delete", "cli_temp", "--force");

			// Verify it's gone
			const listAfter = await cliJson<{ slug: string }[]>("schema", "list");
			expect(listAfter.map((c) => c.slug)).not.toContain("cli_temp");
		});

		it("adds and removes fields", async () => {
			// Create a temp collection
			await cli("schema", "create", "cli_fields", "--label", "Fields Test");

			// Add a field
			const field = await cliJson<{ slug: string; type: string }>(
				"schema",
				"add-field",
				"cli_fields",
				"name",
				"--type",
				"string",
				"--label",
				"Name",
			);
			expect(field.slug).toBe("name");
			expect(field.type).toBe("string");

			// Remove the field
			await cli("schema", "remove-field", "cli_fields", "name");

			// Clean up
			await cli("schema", "delete", "cli_fields", "--force");
		});
	});

	// -----------------------------------------------------------------------
	// Content commands
	// -----------------------------------------------------------------------

	describe("content", () => {
		it("lists content", async () => {
			const result = await cliJson<{ items: { data: Record<string, unknown> }[] }>(
				"content",
				"list",
				"posts",
			);
			expect(result.items.length).toBeGreaterThanOrEqual(2);
		});

		it("gets content by id", async () => {
			const postId = ctx.contentIds["posts"]![0]!;
			const result = await cliJson<{ data: { title: string } }>("content", "get", "posts", postId);
			expect(result.data.title).toBe("First Post");
		});

		it("creates, updates, and deletes content", async () => {
			// Create
			const created = await cliJson<{ id: string; slug: string }>(
				"content",
				"create",
				"posts",
				"--data",
				JSON.stringify({ title: "CLI Post", excerpt: "From CLI" }),
				"--slug",
				"cli-post",
			);
			expect(created.id).toBeDefined();
			expect(created.slug).toBe("cli-post");

			// Update (get first to obtain _rev, then update with it)
			const fetched = await cliJson<{ _rev: string }>("content", "get", "posts", created.id);
			const updated = await cliJson<{ data: { title: string } }>(
				"content",
				"update",
				"posts",
				created.id,
				"--rev",
				fetched._rev,
				"--data",
				JSON.stringify({ title: "Updated CLI Post" }),
			);
			expect(updated.data.title).toBe("Updated CLI Post");

			// Delete
			await cli("content", "delete", "posts", created.id);
		});

		it("publishes and unpublishes content", async () => {
			const item = await cliJson<{ id: string }>(
				"content",
				"create",
				"posts",
				"--data",
				JSON.stringify({ title: "Pub Test" }),
			);

			await cli("content", "publish", "posts", item.id);
			await cli("content", "unpublish", "posts", item.id);

			// Clean up
			await cli("content", "delete", "posts", item.id);
		});
	});

	// -----------------------------------------------------------------------
	// Content lifecycle: schedule and restore
	// -----------------------------------------------------------------------

	describe("content lifecycle", () => {
		it("schedules content for publishing", async () => {
			const item = await cliJson<{ id: string }>(
				"content",
				"create",
				"posts",
				"--data",
				JSON.stringify({ title: "CLI Schedule Test" }),
			);

			// Schedule does not produce JSON output, just a success message
			await cli("content", "schedule", "posts", item.id, "--at", "2027-06-01T09:00:00Z");

			// Verify via get
			const fetched = await cliJson<{ scheduledAt: string }>("content", "get", "posts", item.id);
			expect(fetched.scheduledAt).toBe("2027-06-01T09:00:00Z");

			// Clean up
			await cli("content", "delete", "posts", item.id);
		});

		it("restores a trashed item", async () => {
			const item = await cliJson<{ id: string }>(
				"content",
				"create",
				"posts",
				"--data",
				JSON.stringify({ title: "CLI Restore Test" }),
			);

			// Delete (soft trash)
			await cli("content", "delete", "posts", item.id);

			// Restore
			await cli("content", "restore", "posts", item.id);

			// Should be accessible again (auto-published before deletion, so restored as published)
			const fetched = await cliJson<{ status: string }>("content", "get", "posts", item.id);
			expect(fetched.status).toBe("published");

			// Final cleanup
			await cli("content", "delete", "posts", item.id);
		});
	});

	// -----------------------------------------------------------------------
	// Media commands
	// -----------------------------------------------------------------------

	describe("media", () => {
		it("uploads, lists, gets, and deletes media", async () => {
			// Create a temp file to upload
			const { writeFileSync } = await import("node:fs");
			const { join } = await import("node:path");
			const { tmpdir } = await import("node:os");

			// 1x1 PNG pixel
			const pngBytes = Buffer.from([
				0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44,
				0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x02, 0x00, 0x00, 0x00, 0x90,
				0x77, 0x53, 0xde, 0x00, 0x00, 0x00, 0x0c, 0x49, 0x44, 0x41, 0x54, 0x08, 0xd7, 0x63, 0xf8,
				0xcf, 0xc0, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0xe2, 0x21, 0xbc, 0x33, 0x00, 0x00, 0x00,
				0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82,
			]);
			const tmpFile = join(tmpdir(), "emdash-cli-test.png");
			writeFileSync(tmpFile, pngBytes);

			// Upload
			const uploaded = await cliJson<{ id: string; filename: string }>(
				"media",
				"upload",
				tmpFile,
				"--alt",
				"CLI test image",
			);
			expect(uploaded.id).toBeDefined();
			expect(uploaded.filename).toBe("emdash-cli-test.png");

			// List
			const list = await cliJson<{ items: { id: string }[] }>("media", "list");
			const ids = list.items.map((m) => m.id);
			expect(ids).toContain(uploaded.id);

			// Get
			const fetched = await cliJson<{ id: string; filename: string }>("media", "get", uploaded.id);
			expect(fetched.id).toBe(uploaded.id);

			// Delete
			await cli("media", "delete", uploaded.id);

			// Clean up temp file
			const { unlinkSync } = await import("node:fs");
			unlinkSync(tmpFile);
		});
	});

	// -----------------------------------------------------------------------
	// Search command
	// -----------------------------------------------------------------------

	describe("search", () => {
		it("searches content", async () => {
			// Search should work even if no results (the command shouldn't error)
			const result = await cliJson<unknown[]>("search", "First Post");
			expect(Array.isArray(result)).toBe(true);
		});
	});

	// -----------------------------------------------------------------------
	// Auth commands
	// -----------------------------------------------------------------------

	describe("auth", () => {
		it("whoami returns user info with token auth", async () => {
			const result = await cliJson<{ email: string; role: string }>("whoami");
			expect(result.email).toBe("dev@emdash.local");
			expect(result.role).toBe("admin");
		});
	});

	// -----------------------------------------------------------------------
	// Taxonomy commands
	// -----------------------------------------------------------------------

	describe("taxonomy", () => {
		it("taxonomy list returns valid JSON array", async () => {
			const result = await cliJson<{ name: string }[]>("taxonomy", "list");
			expect(Array.isArray(result)).toBe(true);
			expect(result.length).toBeGreaterThanOrEqual(1);
			const names = result.map((t) => t.name);
			expect(names).toContain("categories");
		});

		it("taxonomy terms returns terms for a taxonomy", async () => {
			const result = await cliJson<{ terms: { slug: string }[] }>(
				"taxonomy",
				"terms",
				"categories",
			);
			expect(result.terms).toBeDefined();
			expect(Array.isArray(result.terms)).toBe(true);
			const slugs = result.terms.map((t) => t.slug);
			expect(slugs).toContain("news");
		});
	});

	// -----------------------------------------------------------------------
	// Menu commands
	// -----------------------------------------------------------------------

	describe("menu", () => {
		it("menu list returns valid JSON array", async () => {
			const result = await cliJson<unknown[]>("menu", "list");
			expect(Array.isArray(result)).toBe(true);
		});
	});
});
