/**
 * Spec-to-PR — paste a plain-English feature spec, get back a draft PR
 * generated by the Claude API.
 *
 *   GET  /:owner/:repo/spec   — form (requires write access)
 *   POST /:owner/:repo/spec   — hands off to lib/spec-to-pr.ts, redirects to
 *                                the new PR on success, re-renders the form
 *                                with an error banner on failure
 *
 * The backend (`createSpecPR` in `src/lib/spec-to-pr.ts`) is being built in
 * parallel. We import it dynamically so this file compiles and its tests
 * pass even if the module is not yet on disk — if the import fails we
 * fall back to a "Backend not available" banner.
 */
import { Hono } from "hono";
import { and, eq } from "drizzle-orm";
import { db } from "../db";
import { repositories, users, issues } from "../db/schema";
import { Layout } from "../views/layout";
import { RepoHeader } from "../views/components";
import { softAuth, requireAuth } from "../middleware/auth";
import type { AuthEnv } from "../middleware/auth";
import { listBranches } from "../git/repository";
import {
  Alert,
  Button,
  Container,
  EmptyState,
  Form,
  FormGroup,
  Select,
  TextArea,
  Text,
} from "../views/ui";

const specs = new Hono<AuthEnv>();

// Tiny inline script that disables the submit button + textarea while the
// request is in-flight so users don't accidentally double-click and trigger
// two 10-30s Claude calls. Rendered as a plain <script> tag.
const DISABLE_ON_SUBMIT_JS = `
(function() {
  var form = document.getElementById('spec-form');
  if (!form) return;
  form.addEventListener('submit', function() {
    var btn = form.querySelector('button[type="submit"]');
    var ta = form.querySelector('textarea[name="spec"]');
    if (btn) {
      btn.disabled = true;
      btn.textContent = 'Working... this can take 10-30s';
    }
    if (ta) ta.readOnly = true;
  });
})();
`;

interface ResolvedRepo {
  ownerId: string;
  ownerUsername: string;
  repoId: string;
  repoName: string;
  defaultBranch: string;
}

async function resolveRepo(
  ownerName: string,
  repoName: string
): Promise<ResolvedRepo | null> {
  try {
    const [ownerRow] = await db
      .select()
      .from(users)
      .where(eq(users.username, ownerName))
      .limit(1);
    if (!ownerRow) return null;
    const [repoRow] = await db
      .select()
      .from(repositories)
      .where(
        and(
          eq(repositories.ownerId, ownerRow.id),
          eq(repositories.name, repoName)
        )
      )
      .limit(1);
    if (!repoRow) return null;
    return {
      ownerId: ownerRow.id,
      ownerUsername: ownerRow.username,
      repoId: repoRow.id,
      repoName: repoRow.name,
      defaultBranch: repoRow.defaultBranch || "main",
    };
  } catch {
    return null;
  }
}

/**
 * Write access check. Gluecron has no collaborator table yet, so "write
 * access" == repo owner. Matches the convention used by repo-settings and
 * ai-explain's regenerate endpoint.
 */
function hasWriteAccess(
  resolved: ResolvedRepo,
  userId: string | undefined
): boolean {
  return !!userId && resolved.ownerId === userId;
}

/**
 * Build the default spec text for an issue-driven generation. Format:
 *
 *   Implement: <title>
 *
 *   <body>
 *
 *   Closes #<n>
 *
 * The trailing `Closes #N` is picked up by `src/lib/close-keywords.ts` (J7)
 * so the issue auto-closes when the AI-generated PR is merged. Body is
 * trimmed and falls back to an empty string if missing. Pure helper —
 * exported for tests.
 */
export function buildSpecFromIssue(input: {
  number: number;
  title: string;
  body: string | null | undefined;
}): string {
  const title = (input.title || "").trim();
  const body = (input.body || "").trim();
  const lines: string[] = [];
  if (title) lines.push(`Implement: ${title}`);
  if (body) {
    lines.push("");
    lines.push(body);
  }
  lines.push("");
  lines.push(`Closes #${input.number}`);
  return lines.join("\n");
}

function SpecForm({
  ownerName,
  repoName,
  branches,
  defaultBranch,
  spec,
  baseRef,
  error,
  fromIssueNumber,
  fromIssueTitle,
}: {
  ownerName: string;
  repoName: string;
  branches: string[];
  defaultBranch: string;
  spec?: string;
  baseRef?: string;
  error?: string;
  fromIssueNumber?: number;
  fromIssueTitle?: string;
}) {
  const branchList = branches.length > 0 ? branches : [defaultBranch];
  const selectedBase = baseRef && branchList.includes(baseRef)
    ? baseRef
    : defaultBranch;
  return (
    <Container maxWidth={820}>
      <div
        class="panel"
        style="padding:14px 16px;margin-bottom:20px;border-left:3px solid var(--accent)"
      >
        <strong>Experimental</strong>
        {" — "}
        AI-generated PRs are draft by default. Review every line before
        merging.
      </div>

      {fromIssueNumber && (
        <Alert variant="info">
          Building from issue{" "}
          <a href={`/${ownerName}/${repoName}/issues/${fromIssueNumber}`}>
            #{fromIssueNumber}
            {fromIssueTitle ? ` — ${fromIssueTitle}` : ""}
          </a>
          . The spec below has been pre-filled from the issue and will
          auto-close it on merge.
        </Alert>
      )}

      <h2 style="margin-bottom:4px">Spec to PR</h2>
      <Text muted style="display:block;margin-bottom:16px">
        Describe a feature in plain English. Claude will draft the code
        changes and open a pull request against the branch you choose.
      </Text>

      {error && <Alert variant="error">{error}</Alert>}

      <Form
        method="post"
        action={`/${ownerName}/${repoName}/spec`}
        id="spec-form"
      >
        <FormGroup label="Feature spec" htmlFor="spec">
          <TextArea
            name="spec"
            id="spec"
            rows={10}
            required
            value={spec || ""}
            placeholder="add a dark mode toggle to the settings page"
          />
        </FormGroup>

        <FormGroup label="Base branch" htmlFor="baseRef">
          <Select name="baseRef" id="baseRef" value={selectedBase}>
            {branchList.map((b) => (
              <option value={b} selected={b === selectedBase}>
                {b}
              </option>
            ))}
          </Select>
        </FormGroup>

        <Button type="submit" variant="primary">
          Generate PR with AI
        </Button>
      </Form>

      <div class="panel" style="margin-top:28px">
        <div
          class="panel-item"
          style="flex-direction:column;align-items:flex-start;gap:4px;padding:14px 16px"
        >
          <strong>How this works</strong>
        </div>
        <div class="panel-item" style="padding:12px 16px">
          <div>
            <strong>1. You write a spec.</strong>
            {" "}
            <Text muted>
              A sentence or a paragraph describing the change you want.
            </Text>
          </div>
        </div>
        <div class="panel-item" style="padding:12px 16px">
          <div>
            <strong>2. Claude drafts the diff.</strong>
            {" "}
            <Text muted>
              We fetch the base branch, run Claude against the repo, and
              commit the proposed changes to a new branch.
            </Text>
          </div>
        </div>
        <div class="panel-item" style="padding:12px 16px">
          <div>
            <strong>3. A draft PR opens.</strong>
            {" "}
            <Text muted>
              You review, edit, and merge on your terms. Nothing lands on
              {" "}
              <code>{selectedBase}</code> automatically.
            </Text>
          </div>
        </div>
      </div>

      <script dangerouslySetInnerHTML={{ __html: DISABLE_ON_SUBMIT_JS }} />
    </Container>
  );
}

specs.get("/:owner/:repo/spec", softAuth, requireAuth, async (c) => {
  const { owner, repo } = c.req.param();
  const user = c.get("user")!;

  const resolved = await resolveRepo(owner, repo);
  if (!resolved) {
    return c.html(
      <Layout title="Not Found" user={user}>
        <EmptyState title="Repository not found">
          <p>No such repository.</p>
        </EmptyState>
      </Layout>,
      404
    );
  }

  if (!hasWriteAccess(resolved, user.id)) {
    return c.html(
      <Layout title="Forbidden" user={user}>
        <RepoHeader owner={owner} repo={repo} />
        <EmptyState title="Write access required">
          <p>You need write access to generate a spec-to-PR on this repository.</p>
        </EmptyState>
      </Layout>,
      403
    );
  }

  let branches: string[] = [];
  try {
    branches = await listBranches(owner, repo);
  } catch {
    branches = [];
  }

  // Optional: pre-fill from an issue. Triggered from the "Build with AI"
  // button on the issue detail page. Silently no-ops on missing/unknown
  // issue so the form still renders.
  let prefilledSpec: string | undefined;
  let fromIssueNumber: number | undefined;
  let fromIssueTitle: string | undefined;
  const fromIssueRaw = c.req.query("fromIssue");
  if (fromIssueRaw) {
    const n = Number.parseInt(fromIssueRaw, 10);
    if (Number.isInteger(n) && n > 0) {
      try {
        const [issueRow] = await db
          .select()
          .from(issues)
          .where(
            and(eq(issues.repositoryId, resolved.repoId), eq(issues.number, n))
          )
          .limit(1);
        if (issueRow) {
          fromIssueNumber = issueRow.number;
          fromIssueTitle = issueRow.title;
          prefilledSpec = buildSpecFromIssue({
            number: issueRow.number,
            title: issueRow.title,
            body: issueRow.body,
          });
        }
      } catch {
        // Pre-fill is a convenience, never block form render.
      }
    }
  }

  return c.html(
    <Layout title={`Spec to PR — ${owner}/${repo}`} user={user}>
      <RepoHeader owner={owner} repo={repo} />
      <SpecForm
        ownerName={owner}
        repoName={repo}
        branches={branches}
        defaultBranch={resolved.defaultBranch}
        spec={prefilledSpec}
        fromIssueNumber={fromIssueNumber}
        fromIssueTitle={fromIssueTitle}
      />
    </Layout>
  );
});

specs.post("/:owner/:repo/spec", softAuth, requireAuth, async (c) => {
  const { owner, repo } = c.req.param();
  const user = c.get("user")!;

  const resolved = await resolveRepo(owner, repo);
  if (!resolved) return c.notFound();

  if (!hasWriteAccess(resolved, user.id)) {
    return c.html(
      <Layout title="Forbidden" user={user}>
        <RepoHeader owner={owner} repo={repo} />
        <EmptyState title="Write access required">
          <p>You need write access to generate a spec-to-PR on this repository.</p>
        </EmptyState>
      </Layout>,
      403
    );
  }

  const body = await c.req.parseBody();
  const spec = String(body.spec || "").trim();
  const baseRef = String(body.baseRef || resolved.defaultBranch).trim()
    || resolved.defaultBranch;

  let branches: string[] = [];
  try {
    branches = await listBranches(owner, repo);
  } catch {
    branches = [];
  }

  function renderWithError(error: string, status: 400 | 500 | 503 = 400) {
    return c.html(
      <Layout title={`Spec to PR — ${owner}/${repo}`} user={user}>
        <RepoHeader owner={owner} repo={repo} />
        <SpecForm
          ownerName={owner}
          repoName={repo}
          branches={branches}
          defaultBranch={resolved!.defaultBranch}
          spec={spec}
          baseRef={baseRef}
          error={error}
        />
      </Layout>,
      status
    );
  }

  if (!spec) {
    return renderWithError("Spec is required.");
  }

  // Dynamically import the backend so this file works even before the
  // sibling branch landing `src/lib/spec-to-pr.ts` is merged. If the module
  // is missing or throws we surface a soft error instead of 500-ing.
  let createSpecPR:
    | ((args: {
        repoId: string;
        spec: string;
        baseRef: string;
        userId: string;
      }) => Promise<
        | { ok: true; prNumber: number }
        | { ok: false; error: string }
      >)
    | null = null;
  try {
    const mod: any = await import("../lib/spec-to-pr");
    createSpecPR =
      (mod && (mod.createSpecPR || (mod.default && mod.default.createSpecPR))) ||
      null;
  } catch {
    createSpecPR = null;
  }

  if (!createSpecPR) {
    return renderWithError(
      "Backend not available — spec-to-PR is not deployed yet. Please try again later.",
      503
    );
  }

  let result:
    | { ok: true; prNumber: number }
    | { ok: false; error: string };
  try {
    result = await createSpecPR({
      repoId: resolved.repoId,
      spec,
      baseRef,
      userId: user.id,
    });
  } catch (err) {
    const msg =
      err instanceof Error ? err.message : "Unexpected error generating PR.";
    return renderWithError(`Failed to generate PR: ${msg}`, 500);
  }

  if (!result || !result.ok) {
    const msg = (result && "error" in result && result.error) || "Unknown error.";
    return renderWithError(`Failed to generate PR: ${msg}`);
  }

  return c.redirect(`/${owner}/${repo}/pulls/${result.prNumber}`);
});

export default specs;
