/**
 * Build Studio Full Lifecycle Demo
 *
 * Phase-driven test that reads every AI response and adapts.
 * The phase indicator (data-current-phase) is the source of truth —
 * the test never assumes a phase transition happened without checking.
 *
 * Run with: DPF_ADMIN_PASSWORD=<password> npx playwright test e2e/09-build-lifecycle-demo.spec.ts --headed
 */
import { test, expect } from "@playwright/test";
import {
  loginToDPF,
  sendAndWait,
  approveAllProposals,
  waitForCoworkerIdle,
  extractBuildId,
  extractLastResponse,
} from "./helpers";

const FEATURE_TITLE = "Customer Complaint Tracker";
const MAX_PHASE_ATTEMPTS = 12;
const MIN_RESPONSE_LENGTH = 50;

// ─── Response Analysis ─────────────────────────────────────────────────────

function isCannedFallback(response: string): boolean {
  return response.includes("wasn't able to help") ||
    response.includes("No AI provider was matched") ||
    response.includes("Unable to process");
}

function isAskingQuestion(response: string): boolean {
  return [/should I/i, /do you want/i, /which approach/i, /could you clarify/i, /want me to/i]
    .some((p) => p.test(response));
}

function hasBlocker(response: string): boolean {
  return [/can'?t\s+(safely\s+)?(modify|add|create|edit|write)/i, /blocker/i, /failed to/i, /error:/i]
    .some((p) => p.test(response));
}

function didComplete(response: string, keywords: string[]): boolean {
  return keywords.some((kw) => response.toLowerCase().includes(kw.toLowerCase()));
}

async function getCurrentPhase(page: import("@playwright/test").Page): Promise<string> {
  // The server-side agentic loop may auto-advance phases without the UI knowing.
  // Reload to get fresh phase data, then re-select the active build.
  await page.reload({ waitUntil: "networkidle", timeout: 15_000 }).catch(() => {});
  await page.waitForTimeout(1_000);

  // After reload, click the build tab to re-enter the build (shows phase indicator)
  const buildTab = page.locator('button').filter({ hasText: /FB-/ }).first();
  if (await buildTab.isVisible({ timeout: 3_000 }).catch(() => false)) {
    await buildTab.click();
    await page.waitForTimeout(1_500);
  }

  return page.evaluate(() => {
    const indicator = document.querySelector('[data-testid="phase-indicator"]');
    return indicator?.getAttribute("data-current-phase") ?? "unknown";
  });
}

// ─── Phase-Aware Send ──────────────────────────────────────────────────────

/**
 * Send a message, read the response, validate it's not a canned fallback.
 * Returns the response text. Fails the test if routing is broken.
 */
async function sendAndRead(
  page: import("@playwright/test").Page,
  message: string,
  label: string,
  timeoutMs = 180_000,
): Promise<string> {
  const response = await sendAndWait(page, message, timeoutMs);
  await approveAllProposals(page);

  // Detect routing errors. Give one retry after a pause (may be rate limit).
  if (isCannedFallback(response)) {
    console.log(`[${label}] Got canned fallback — waiting 15s and retrying (may be rate limit)...`);
    await page.waitForTimeout(15_000);
    const retryResponse = await sendAndWait(page, message, timeoutMs);
    await approveAllProposals(page);
    if (isCannedFallback(retryResponse)) {
      throw new Error(
        `[${label}] AI provider routing broken — got canned fallback twice: "${retryResponse.slice(0, 150)}". ` +
        `Fix provider config before re-running test.`,
      );
    }
    return retryResponse;
  }

  const phase = await getCurrentPhase(page);
  console.log(`[${label}] Phase: ${phase} | Response (${response.length} chars): ${response.slice(0, 200)}`);
  return response;
}

/**
 * Wait for a specific phase, retrying with adaptive messages.
 * Returns when the target phase (or later) is reached.
 */
async function waitForPhase(
  page: import("@playwright/test").Page,
  targetPhase: string,
  promptFn: (attempt: number, currentPhase: string, lastResponse: string) => string,
  label: string,
  timeoutMs = 300_000,
): Promise<string> {
  const phaseOrder = ["ideate", "plan", "build", "review", "ship", "complete"];
  const targetIdx = phaseOrder.indexOf(targetPhase);

  let lastResponse = "";
  for (let attempt = 0; attempt < MAX_PHASE_ATTEMPTS; attempt++) {
    // Wait for the AI to finish any in-progress work before checking phase.
    // The AI may be auto-advancing from a previous message's tool calls.
    await waitForCoworkerIdle(page, timeoutMs);
    await page.waitForTimeout(2_000); // Let DOM settle after phase change

    const currentPhase = await getCurrentPhase(page);
    const currentIdx = phaseOrder.indexOf(currentPhase);

    if (currentIdx >= targetIdx) {
      console.log(`[${label}] Reached phase: ${currentPhase}`);
      return lastResponse;
    }

    const message = promptFn(attempt, currentPhase, lastResponse);
    lastResponse = await sendAndRead(page, message, `${label}:${attempt}`, timeoutMs);
  }

  const finalPhase = await getCurrentPhase(page);
  throw new Error(`[${label}] Failed to reach phase "${targetPhase}" after ${MAX_PHASE_ATTEMPTS} attempts. Stuck at: ${finalPhase}`);
}

// ─── Test ──────────────────────────────────────────────────────────────────

test.describe("Build Studio Lifecycle Demo", () => {
  test("full feature build: create, design, plan, build, deploy, verify", async ({ page }) => {
    test.setTimeout(2_100_000); // 35 minutes — AI interactions + ship phase tool chain

    // ━━━ Step 1: Login & Navigate ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    console.log("\n=== STEP 1: Login ===");
    await loginToDPF(page);

    console.log("\n=== STEP 2: Navigate to Build Studio ===");
    await page.goto("/build");
    await page.waitForLoadState("networkidle", { timeout: 15_000 }).catch(() => {});
    const featureInput = page.locator('input[placeholder*="feature" i]');
    await expect(featureInput).toBeVisible({ timeout: 15_000 });

    // ━━━ Step 2: Create Feature ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    console.log("\n=== STEP 3: Create Feature ===");
    await featureInput.fill(FEATURE_TITLE);
    await page.locator("button").filter({ hasText: /^New$/i }).click();

    const panel = page.locator('[data-agent-panel="true"]');
    await expect(panel).toBeVisible({ timeout: 15_000 });
    await expect(panel.locator("text=Software Engineer").first()).toBeVisible({ timeout: 5_000 });

    // Wait for the auto-message (feature creation triggers an initial AI response)
    await waitForCoworkerIdle(page, 120_000);
    await page.waitForTimeout(2_000);
    await approveAllProposals(page);

    const initialPhase = await getCurrentPhase(page);
    console.log(`[create] Initial phase: ${initialPhase}`);
    await page.screenshot({ path: "e2e-report/demo-03-feature-created.png" });

    // ━━━ Step 3: Ideate → Plan ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    console.log("\n=== IDEATE PHASE ===");

    // First message: give the AI all requirements upfront
    let response = await sendAndRead(page,
      "Design this feature now. Here are the exact requirements — do not ask for clarification:\n" +
      "1) A simple complaints list page at /complaints showing all complaints with status badges (open, investigating, resolved, closed)\n" +
      "2) A form to submit a new complaint with fields: customer name (text), description (textarea), severity (select: low/medium/high/critical), category (text)\n" +
      "3) In-memory data store (no database changes needed — use a simple array or Map for this demo)\n" +
      "4) Use existing platform UI patterns (Tailwind, shadcn components if available)\n" +
      "Save the design document now.",
      "ideate",
    );

    expect(response.length, "Ideate response too short — likely canned fallback").toBeGreaterThan(MIN_RESPONSE_LENGTH);
    await page.screenshot({ path: "e2e-report/demo-04-ideate.png" });

    // Drive to plan phase — the gate requires designDoc + designReview
    await waitForPhase(page, "plan", (attempt, phase, lastResp) => {
      if (phase === "ideate" && isAskingQuestion(lastResp)) {
        return "Do not ask questions. Use the requirements I already gave you. " +
          "Save the design document and then review it so we can advance to the plan phase.";
      }
      if (phase === "ideate" && attempt === 0) {
        return "Save the design document, then submit it for review so the phase can advance to plan. " +
          "Use saveBuildEvidence to save the design, then reviewDesignDoc to review it.";
      }
      if (phase === "ideate" && lastResp.includes("don't have")) {
        // AI can't find the tool — list what it should have
        return "You should have the reviewDesignDoc tool available in the ideate phase. " +
          "Check your tools list — it's camelCase, not snake_case. Call reviewDesignDoc now.";
      }
      if (phase === "ideate") {
        return "The phase cannot advance without a design review. " +
          "Call reviewDesignDoc (camelCase) with the buildId to submit the review. It will auto-advance when both design doc and review are saved.";
      }
      return "Advance to the plan phase now.";
    }, "ideate→plan");

    await page.screenshot({ path: "e2e-report/demo-05-plan.png" });

    // ━━━ Step 4: Plan → Build ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    console.log("\n=== PLAN PHASE ===");

    response = await sendAndRead(page,
      "Create the implementation plan. This is a single-file feature — no database changes needed:\n" +
      "- One new file: apps/web/app/(shell)/complaints/page.tsx\n" +
      "- Contains: complaint list with status badges, submit form, in-memory state using React useState\n" +
      "- No Prisma schema changes, no API routes, no migrations\n" +
      "Save the plan with saveBuildEvidence, then review it with reviewBuildPlan.",
      "plan",
    );

    // Gate requires buildPlan + planReview
    await waitForPhase(page, "build", (attempt, phase, lastResp) => {
      if (hasBlocker(lastResp)) {
        return "Ignore blockers. This is a simple single-page feature with in-memory data. " +
          "Save the plan and review it to advance.";
      }
      if (phase === "plan" && attempt === 0) {
        return "Save the plan with saveBuildEvidence, then review it with reviewBuildPlan. " +
          "Both are required before the phase can advance to build.";
      }
      if (phase === "plan" && lastResp.includes("don't have")) {
        return "You should have reviewBuildPlan available in the plan phase. " +
          "It's camelCase. Call reviewBuildPlan now to submit the review.";
      }
      if (phase === "plan") {
        return "The phase cannot advance without a plan review. Call reviewBuildPlan (camelCase) now.";
      }
      return "Advance to the build phase.";
    }, "plan→build");

    await page.screenshot({ path: "e2e-report/demo-06-build-start.png" });

    // ━━━ Step 5: Build → Review ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    console.log("\n=== BUILD PHASE ===");

    response = await sendAndRead(page,
      "Generate the code in the sandbox now. Use the write_sandbox_file tool to create:\n" +
      "apps/web/app/(shell)/complaints/page.tsx\n\n" +
      "This should be a React 'use client' component with:\n" +
      "- useState for an in-memory complaints array\n" +
      "- A form to add complaints (name, description, severity, category)\n" +
      "- A list showing complaints with status badges\n" +
      "- Tailwind CSS styling\n\n" +
      "Do NOT modify the Prisma schema. Do NOT create API routes. This is a client-side only page.\n" +
      "After creating the file, run the sandbox typecheck to verify.",
      "build",
      300_000,
    );

    expect(response.length, "Build response too short — likely canned fallback").toBeGreaterThan(MIN_RESPONSE_LENGTH);

    // Check if code was written — adapt if not
    if (!didComplete(response, ["created", "generated", "wrote", "file", "page.tsx", "write_sandbox_file"])) {
      console.log("[build] Code not confirmed written, following up...");
      response = await sendAndRead(page,
        "Use the write_sandbox_file tool to create apps/web/app/(shell)/complaints/page.tsx now. " +
        "Do not explain — just write the file.",
        "build-retry",
        300_000,
      );
    }

    // If AI hasn't run typecheck, ask for it
    if (!didComplete(response, ["typecheck", "type check", "no errors", "compiles"])) {
      console.log("[build] Requesting typecheck...");
      response = await sendAndRead(page,
        "Run the sandbox typecheck: run_sandbox_command with 'pnpm --filter web exec tsc --noEmit'.",
        "build-typecheck",
        120_000,
      );
    }

    await page.screenshot({ path: "e2e-report/demo-07-built.png" });
    const buildId = await extractBuildId(page);
    console.log(`[build] Build ID: ${buildId}`);

    // Drive to review phase — may need to fix typecheck errors first
    await waitForPhase(page, "review", (attempt, phase, lastResp) => {
      if (phase !== "build") return "Advance to review phase.";

      // First few attempts: run typecheck and fix errors
      if (attempt === 0) {
        return "Run the typecheck with run_sandbox_command: 'cd /workspace/apps/web && npx tsc --noEmit'. " +
          "If there are errors, fix them. Do NOT create API routes or use @prisma/client.";
      }
      // If AI reports specific errors, let it fix them
      if (lastResp.includes("Fixed") || lastResp.includes("fixed") || lastResp.includes("Let me fix")) {
        return "Good progress. Run the typecheck again to see if more errors remain. Keep fixing until it passes clean.";
      }
      if (lastResp.includes("blocker") || lastResp.includes("not responding")) {
        return "Try running the typecheck directly: run_sandbox_command with command 'cd /workspace/apps/web && npx tsc --noEmit 2>&1 | head -20'. " +
          "Read the output and fix the errors shown.";
      }
      // Generic: keep trying
      return "Run the typecheck and fix any errors. Use run_sandbox_command with 'cd /workspace/apps/web && npx tsc --noEmit 2>&1 | head -20' " +
        "to see the errors, then fix them with edit_sandbox_file or write_sandbox_file.";
    }, "build→review", 300_000);

    await page.screenshot({ path: "e2e-report/demo-08-review.png" });

    // ━━━ Step 6: Review → Ship ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    console.log("\n=== REVIEW PHASE ===");

    response = await sendAndRead(page,
      "Evaluate the build. Save acceptance criteria using saveBuildEvidence with field 'acceptanceMet' " +
      "and value: [{criterion: 'Complaints list page', met: true, evidence: 'Page created'}, " +
      "{criterion: 'Submit form', met: true, evidence: 'Form with all fields'}, " +
      "{criterion: 'Status badges', met: true, evidence: 'Badges rendered'}, " +
      "{criterion: 'In-memory storage', met: true, evidence: 'useState array'}]. " +
      "Then advance to ship.",
      "review",
      300_000,
    );

    await waitForPhase(page, "ship", (attempt, phase, lastResp) => {
      if (phase === "review" && attempt === 0) {
        return "Save acceptance criteria with saveBuildEvidence field 'acceptanceMet' — all criteria met. Then advance to ship.";
      }
      if (phase === "review") {
        return "All acceptance criteria are met. The phase gate needs acceptanceMet saved via saveBuildEvidence. Do it now.";
      }
      return "Advance to ship phase.";
    }, "review→ship", 300_000);

    await page.screenshot({ path: "e2e-report/demo-09-ship.png" });

    // ━━━ Step 7: Ship ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    console.log("\n=== SHIP PHASE ===");

    // Ship phase: tell the AI to run the full ship sequence.
    // The AI will propose deploy_feature → register_digital_product → create_build_epic → execute_promotion.
    // Each proposal gets auto-approved, and the auto-follow-up "I approved X. What's next?" drives the chain.
    response = await sendAndRead(page,
      "Ship this feature now. Run the full ship sequence: " +
      "1) deploy_feature to extract the diff, " +
      "2) register_digital_product_from_build with name 'Customer Complaint Tracker' and portfolioSlug 'default', " +
      "3) create_build_epic for backlog tracking, " +
      "4) execute_promotion to deploy to production. " +
      "Start with deploy_feature now.",
      "ship:start",
      300_000,
    );

    // The AI may need multiple turns to complete the ship sequence.
    // Keep approving proposals and reading responses until done or timeout.
    for (let shipStep = 0; shipStep < 8; shipStep++) {
      const approved = await approveAllProposals(page, 300_000);
      if (approved === 0) {
        // No proposals — AI may have finished or need prompting
        if (didComplete(response, ["promoted", "deployed", "promotion complete", "production"])) {
          console.log("[ship] Ship sequence complete!");
          break;
        }
        // Ask AI to continue
        response = await sendAndRead(page,
          "Continue the ship sequence. What's the next step?",
          `ship:step${shipStep}`,
          300_000,
        );
      } else {
        // Proposals approved — wait for follow-up response
        await waitForCoworkerIdle(page, 300_000);
        response = await extractLastResponse(page);
        console.log(`[ship:step${shipStep}] Approved ${approved}, response: ${response.slice(0, 200)}`);
      }
    }

    await page.screenshot({ path: "e2e-report/demo-10-shipped.png" });

    // ━━━ Step 8: Verify ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    console.log("\n=== VERIFY ===");

    await page.goto("/ops");
    await page.waitForLoadState("networkidle", { timeout: 15_000 }).catch(() => {});
    await page.waitForTimeout(2_000);
    const backlogText = await page.textContent("body");
    const hasBacklogItem = backlogText?.toLowerCase().includes("complaint") ?? false;
    console.log(`[verify] Feature in backlog: ${hasBacklogItem}`);

    const healthResp = await page.request.get("/api/health");
    console.log(`[verify] Portal health: ${healthResp.status()}`);
    expect(healthResp.status()).toBe(200);

    await page.screenshot({ path: "e2e-report/demo-11-final.png", fullPage: true });

    console.log("\n=== DEMO COMPLETE ===");
    console.log(`Build ID: ${buildId}`);
  });
});
