/**
 * Integration test for metadata lifecycle — real filesystem operations.
 *
 * Tests the full metadata CRUD cycle (write, read, update, list, delete/archive)
 * and concurrent access patterns using @composio/ao-core metadata functions
 * with real filesystem I/O.
 */

import { mkdtemp, rm } from "node:fs/promises";
import { existsSync, readdirSync, readFileSync, mkdirSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterAll, beforeAll, describe, expect, it } from "vitest";
import {
  writeMetadata,
  readMetadata,
  readMetadataRaw,
  updateMetadata,
  deleteMetadata,
  listMetadata,
  type SessionMetadata,
} from "@composio/ao-core";

describe("metadata lifecycle (real filesystem)", () => {
  let tmpDir: string;

  beforeAll(async () => {
    tmpDir = await mkdtemp(join(tmpdir(), "ao-meta-lifecycle-"));
  });

  afterAll(async () => {
    if (tmpDir) {
      await rm(tmpDir, { recursive: true, force: true }).catch(() => {});
    }
  });

  it("write + read round-trip preserves all fields", () => {
    const sessionsDir = join(tmpDir, "test-roundtrip");
    mkdirSync(sessionsDir, { recursive: true });

    const metadata: SessionMetadata = {
      worktree: "/tmp/wt/session-1",
      branch: "feat/INT-100",
      status: "working",
      tmuxName: "abc123-session-1",
      issue: "INT-100",
      pr: "https://github.com/org/repo/pull/42",
      summary: "Implementing feature INT-100",
      project: "my-project",
      createdAt: "2026-01-01T00:00:00.000Z",
      dashboardPort: 4000,
    };

    writeMetadata(sessionsDir, "session-1", metadata);
    const result = readMetadata(sessionsDir, "session-1");

    expect(result).not.toBeNull();
    expect(result!.worktree).toBe(metadata.worktree);
    expect(result!.branch).toBe(metadata.branch);
    expect(result!.status).toBe(metadata.status);
    expect(result!.tmuxName).toBe(metadata.tmuxName);
    expect(result!.issue).toBe(metadata.issue);
    expect(result!.pr).toBe(metadata.pr);
    expect(result!.summary).toBe(metadata.summary);
    expect(result!.project).toBe(metadata.project);
    expect(result!.createdAt).toBe(metadata.createdAt);
    expect(result!.dashboardPort).toBe(4000);
  });

  it("readMetadataRaw returns all key-value pairs", () => {
    const sessionsDir = join(tmpDir, "test-raw");
    mkdirSync(sessionsDir, { recursive: true });

    writeMetadata(sessionsDir, "session-raw", {
      worktree: "/w",
      branch: "main",
      status: "idle",
      project: "proj",
    });

    const raw = readMetadataRaw(sessionsDir, "session-raw");
    expect(raw).not.toBeNull();
    expect(raw!["worktree"]).toBe("/w");
    expect(raw!["branch"]).toBe("main");
    expect(raw!["status"]).toBe("idle");
    expect(raw!["project"]).toBe("proj");
  });

  it("read returns null for non-existent session", () => {
    const sessionsDir = join(tmpDir, "test-nonexistent");
    mkdirSync(sessionsDir, { recursive: true });

    const result = readMetadata(sessionsDir, "no-such-session");
    expect(result).toBeNull();
  });

  it("updateMetadata merges fields into existing file", () => {
    const sessionsDir = join(tmpDir, "test-update");
    mkdirSync(sessionsDir, { recursive: true });

    writeMetadata(sessionsDir, "session-upd", {
      worktree: "/w",
      branch: "feat/x",
      status: "spawning",
      project: "proj",
    });

    updateMetadata(sessionsDir, "session-upd", {
      status: "working",
      pr: "https://github.com/org/repo/pull/99",
    });

    const result = readMetadata(sessionsDir, "session-upd");
    expect(result!.status).toBe("working");
    expect(result!.pr).toBe("https://github.com/org/repo/pull/99");
    // Original fields preserved
    expect(result!.worktree).toBe("/w");
    expect(result!.branch).toBe("feat/x");
    expect(result!.project).toBe("proj");
  });

  it("updateMetadata removes keys set to empty string", () => {
    const sessionsDir = join(tmpDir, "test-remove-key");
    mkdirSync(sessionsDir, { recursive: true });

    writeMetadata(sessionsDir, "session-rmk", {
      worktree: "/w",
      branch: "main",
      status: "working",
      summary: "Remove me",
    });

    updateMetadata(sessionsDir, "session-rmk", {
      summary: "",
    });

    const raw = readMetadataRaw(sessionsDir, "session-rmk");
    expect(raw!["summary"]).toBeUndefined();
    expect(raw!["worktree"]).toBe("/w");
  });

  it("updateMetadata creates file if it does not exist", () => {
    const sessionsDir = join(tmpDir, "test-update-create");
    mkdirSync(sessionsDir, { recursive: true });

    updateMetadata(sessionsDir, "session-new", {
      worktree: "/w",
      branch: "main",
      status: "spawning",
    });

    const result = readMetadata(sessionsDir, "session-new");
    expect(result).not.toBeNull();
    expect(result!.status).toBe("spawning");
  });

  it("listMetadata returns session IDs, excluding archive directory", () => {
    const sessionsDir = join(tmpDir, "test-list");
    mkdirSync(sessionsDir, { recursive: true });

    writeMetadata(sessionsDir, "session-a", { worktree: "/a", branch: "a", status: "idle" });
    writeMetadata(sessionsDir, "session-b", { worktree: "/b", branch: "b", status: "working" });
    writeMetadata(sessionsDir, "session-c", { worktree: "/c", branch: "c", status: "done" });

    const ids = listMetadata(sessionsDir);
    expect(ids).toContain("session-a");
    expect(ids).toContain("session-b");
    expect(ids).toContain("session-c");
    expect(ids).not.toContain("archive");
  });

  it("deleteMetadata with archive=true moves file to archive/", () => {
    const sessionsDir = join(tmpDir, "test-archive");
    mkdirSync(sessionsDir, { recursive: true });

    writeMetadata(sessionsDir, "session-del", {
      worktree: "/w",
      branch: "main",
      status: "working",
    });

    expect(existsSync(join(sessionsDir, "session-del"))).toBe(true);

    deleteMetadata(sessionsDir, "session-del", true);

    // Original file removed
    expect(existsSync(join(sessionsDir, "session-del"))).toBe(false);

    // Archive created
    const archiveDir = join(sessionsDir, "archive");
    expect(existsSync(archiveDir)).toBe(true);
    const archived = readdirSync(archiveDir);
    expect(archived.length).toBe(1);
    expect(archived[0]).toMatch(/^session-del_/);

    // Archive content matches original
    const content = readFileSync(join(archiveDir, archived[0]), "utf-8");
    expect(content).toContain("worktree=/w");
    expect(content).toContain("branch=main");
  });

  it("deleteMetadata with archive=false permanently removes file", () => {
    const sessionsDir = join(tmpDir, "test-permanent-delete");
    mkdirSync(sessionsDir, { recursive: true });

    writeMetadata(sessionsDir, "session-gone", {
      worktree: "/w",
      branch: "main",
      status: "done",
    });

    deleteMetadata(sessionsDir, "session-gone", false);

    expect(existsSync(join(sessionsDir, "session-gone"))).toBe(false);
    expect(existsSync(join(sessionsDir, "archive"))).toBe(false);
  });

  it("deleteMetadata is a no-op for non-existent session", () => {
    const sessionsDir = join(tmpDir, "test-delete-noop");
    mkdirSync(sessionsDir, { recursive: true });

    // Should not throw
    deleteMetadata(sessionsDir, "no-such-session", true);
    deleteMetadata(sessionsDir, "no-such-session", false);
  });

  it("validates session ID rejects path traversal attempts", () => {
    const sessionsDir = join(tmpDir, "test-validation");
    mkdirSync(sessionsDir, { recursive: true });

    expect(() => readMetadata(sessionsDir, "../escape")).toThrow("Invalid session ID");
    expect(() => readMetadata(sessionsDir, "foo/bar")).toThrow("Invalid session ID");
    expect(() => readMetadata(sessionsDir, "foo bar")).toThrow("Invalid session ID");
  });

  describe("concurrent access patterns", () => {
    it("concurrent writes to different sessions do not interfere", async () => {
      const sessionsDir = join(tmpDir, "test-concurrent-different");
      mkdirSync(sessionsDir, { recursive: true });

      // Write 20 sessions concurrently
      const count = 20;
      const promises = Array.from({ length: count }, (_, i) =>
        Promise.resolve().then(() => {
          writeMetadata(sessionsDir, `concurrent-${i}`, {
            worktree: `/w/${i}`,
            branch: `branch-${i}`,
            status: "working",
            issue: `ISSUE-${i}`,
          });
        }),
      );

      await Promise.all(promises);

      // Verify all sessions were written correctly
      const ids = listMetadata(sessionsDir);
      expect(ids.length).toBe(count);

      for (let i = 0; i < count; i++) {
        const meta = readMetadata(sessionsDir, `concurrent-${i}`);
        expect(meta).not.toBeNull();
        expect(meta!.worktree).toBe(`/w/${i}`);
        expect(meta!.branch).toBe(`branch-${i}`);
        expect(meta!.issue).toBe(`ISSUE-${i}`);
      }
    });

    it("concurrent updates to the same session preserve last-write wins", async () => {
      const sessionsDir = join(tmpDir, "test-concurrent-same");
      mkdirSync(sessionsDir, { recursive: true });

      // Create initial session
      writeMetadata(sessionsDir, "shared-session", {
        worktree: "/w",
        branch: "main",
        status: "spawning",
      });

      // Rapidly update the same session from multiple "tasks"
      const updates = Array.from({ length: 10 }, (_, i) =>
        Promise.resolve().then(() => {
          updateMetadata(sessionsDir, "shared-session", {
            status: `status-${i}`,
            summary: `Update ${i}`,
          });
        }),
      );

      await Promise.all(updates);

      // Verify the file is valid (not corrupted)
      const result = readMetadata(sessionsDir, "shared-session");
      expect(result).not.toBeNull();
      expect(result!.worktree).toBe("/w");
      expect(result!.branch).toBe("main");
      // Status should be one of the updates (last-write-wins)
      expect(result!.status).toMatch(/^status-\d$/);
      expect(result!.summary).toMatch(/^Update \d$/);
    });
  });

  describe("dashboardPort serialization", () => {
    it("preserves dashboardPort through write/read cycle", () => {
      const sessionsDir = join(tmpDir, "test-dashboard-port");
      mkdirSync(sessionsDir, { recursive: true });

      writeMetadata(sessionsDir, "port-session", {
        worktree: "/w",
        branch: "main",
        status: "working",
        dashboardPort: 4567,
      });

      const result = readMetadata(sessionsDir, "port-session");
      expect(result!.dashboardPort).toBe(4567);
    });

    it("omits dashboardPort when undefined", () => {
      const sessionsDir = join(tmpDir, "test-no-dashboard-port");
      mkdirSync(sessionsDir, { recursive: true });

      writeMetadata(sessionsDir, "no-port-session", {
        worktree: "/w",
        branch: "main",
        status: "working",
      });

      const raw = readMetadataRaw(sessionsDir, "no-port-session");
      expect(raw!["dashboardPort"]).toBeUndefined();

      const result = readMetadata(sessionsDir, "no-port-session");
      expect(result!.dashboardPort).toBeUndefined();
    });
  });
});
