import { describe, test, expect, beforeEach, afterEach } from "bun:test";
import {
  beginChange,
  commitChange,
  rollbackChange,
  hasActiveTransaction,
  getActiveTransaction,
  getTransactionStatus,
  listChanges,
} from "../../packages/core/src/change";
import { rm, mkdir, writeFile } from "fs/promises";
import path from "path";

const TEST_DIR = path.join(import.meta.dir, ".test-transaction");

describe("Transaction", () => {
  beforeEach(async () => {
    // 테스트 디렉토리 생성
    await rm(TEST_DIR, { recursive: true, force: true });
    await mkdir(TEST_DIR, { recursive: true });
    await mkdir(path.join(TEST_DIR, "spec", "slots"), { recursive: true });
    await mkdir(path.join(TEST_DIR, "spec", "history", "snapshots"), { recursive: true });

    // 테스트용 manifest 생성
    await writeFile(
      path.join(TEST_DIR, "spec", "routes.manifest.json"),
      JSON.stringify({
        version: 1,
        routes: [
          {
            id: "home",
            pattern: "/",
            kind: "page",
            module: "server/home.ts",
            componentModule: "web/home.tsx",
          },
        ],
      })
    );
  });

  afterEach(async () => {
    await rm(TEST_DIR, { recursive: true, force: true });
  });

  test("beginChange creates a new transaction", async () => {
    const change = await beginChange(TEST_DIR, { message: "Test change" });

    expect(change.id).toMatch(/^\d{8}-\d{6}-[a-z0-9]{3}$/);
    expect(change.status).toBe("active");
    expect(change.message).toBe("Test change");
    expect(change.snapshotId).toBeTruthy();

    const hasActive = await hasActiveTransaction(TEST_DIR);
    expect(hasActive).toBe(true);

    const activeChange = await getActiveTransaction(TEST_DIR);
    expect(activeChange?.id).toBe(change.id);
  });

  test("beginChange throws if transaction already active", async () => {
    await beginChange(TEST_DIR, { message: "First" });

    await expect(beginChange(TEST_DIR, { message: "Second" })).rejects.toThrow(
      /이미 활성화된 트랜잭션이 있습니다/
    );
  });

  test("commitChange commits the active transaction", async () => {
    const change = await beginChange(TEST_DIR, { message: "Test" });
    const result = await commitChange(TEST_DIR);

    expect(result.success).toBe(true);
    expect(result.changeId).toBe(change.id);
    expect(result.message).toBe("Test");

    const hasActive = await hasActiveTransaction(TEST_DIR);
    expect(hasActive).toBe(false);

    const changes = await listChanges(TEST_DIR);
    const committed = changes.find((c) => c.id === change.id);
    expect(committed?.status).toBe("committed");
  });

  test("commitChange throws if no active transaction", async () => {
    await expect(commitChange(TEST_DIR)).rejects.toThrow(/활성화된 트랜잭션이 없습니다/);
  });

  test("rollbackChange restores to snapshot state", async () => {
    // 원본 manifest 저장
    const originalManifest = await Bun.file(
      path.join(TEST_DIR, "spec", "routes.manifest.json")
    ).json();

    // 트랜잭션 시작
    const change = await beginChange(TEST_DIR, { message: "Risky change" });

    // manifest 수정
    await writeFile(
      path.join(TEST_DIR, "spec", "routes.manifest.json"),
      JSON.stringify({ version: 999, routes: [] })
    );

    // 롤백
    const result = await rollbackChange(TEST_DIR);

    expect(result.success).toBe(true);
    expect(result.changeId).toBe(change.id);
    expect(result.restoreResult.restoredFiles).toContain("routes.manifest.json");

    // 복원 확인
    const restoredManifest = await Bun.file(
      path.join(TEST_DIR, "spec", "routes.manifest.json")
    ).json();
    expect(restoredManifest.version).toBe(originalManifest.version);
    expect(restoredManifest.routes).toHaveLength(originalManifest.routes.length);

    // 상태 확인
    const hasActive = await hasActiveTransaction(TEST_DIR);
    expect(hasActive).toBe(false);

    const changes = await listChanges(TEST_DIR);
    const rolledBack = changes.find((c) => c.id === change.id);
    expect(rolledBack?.status).toBe("rolled_back");
  });

  test("rollbackChange throws if no active transaction", async () => {
    await expect(rollbackChange(TEST_DIR)).rejects.toThrow(/롤백할 트랜잭션이 없습니다/);
  });

  test("getTransactionStatus returns correct status", async () => {
    // 활성 트랜잭션 없을 때
    const status1 = await getTransactionStatus(TEST_DIR);
    expect(status1.state.active).toBe(false);
    expect(status1.change).toBeNull();

    // 트랜잭션 시작 후
    const change = await beginChange(TEST_DIR, { message: "Test" });
    const status2 = await getTransactionStatus(TEST_DIR);
    expect(status2.state.active).toBe(true);
    expect(status2.state.changeId).toBe(change.id);
    expect(status2.change?.message).toBe("Test");
  });

  test("autoGenerated flag is set correctly", async () => {
    const change = await beginChange(TEST_DIR, {
      message: "Auto-correct",
      autoGenerated: true,
    });

    expect(change.autoGenerated).toBe(true);

    const activeChange = await getActiveTransaction(TEST_DIR);
    expect(activeChange?.autoGenerated).toBe(true);
  });
});
