import { Check, ChevronLeft, ChevronRight, Eye, Plus, RotateCcw, X } from "lucide-react";
import { FormEvent, useCallback, useEffect, useState } from "react";
import { Link } from "react-router-dom";
import { toast } from "sonner";

import { api } from "../api";
import { parseJson, STATUSES } from "../constants";
import { DateTime } from "../components/DateTime";
import { EmptyState } from "../components/EmptyState";
import { JsonField } from "../components/JsonField";
import { Modal } from "../components/Modal";
import { SelectField, SelectOption } from "../components/SelectField";
import { StatusBadge } from "../components/StatusBadge";
import type { Job, JobStatus } from "../types";

const jobTypeOptions: SelectOption[] = [
  { value: "demo.success", label: "Successful" },
  { value: "demo.fail", label: "Failing" },
  { value: "demo.slow", label: "Slow with renewal" },
  { value: "demo.timeout", label: "Timeout without renewal" },
  { value: "demo.flaky", label: "Flaky retry" }
];

const pageSizeOptions: SelectOption[] = [
  { value: "10", label: "10 / page" },
  { value: "25", label: "25 / page" },
  { value: "50", label: "50 / page" }
];

function demoPayload(type: string) {
  if (type === "demo.slow") return { sleep_seconds: 8 };
  if (type === "demo.timeout") return { sleep_seconds: 45 };
  if (type === "demo.flaky") return { fail_until_attempt: 2 };
  if (type === "demo.fail") return { error: "Intentional demo failure" };
  return { sleep_seconds: 1 };
}

function attemptState(job: Job, attemptNumber: number) {
  if (attemptNumber > job.attempts) return "empty";
  if (job.status === "succeeded") return attemptNumber === job.attempts ? "succeeded" : "failed";
  if (job.status === "running" && attemptNumber === job.attempts) return "running";
  return "failed";
}

function AttemptMeter({ job }: { job: Job }) {
  return (
    <div className="attempt-meter" aria-label={`${job.attempts} of ${job.max_attempts} attempts used`}>
      {Array.from({ length: job.max_attempts }, (_, index) => {
        const attemptNumber = index + 1;
        const state = attemptState(job, attemptNumber);
        return (
          <span className={`attempt-square ${state}`} key={attemptNumber} title={`Attempt ${attemptNumber}: ${state}`}>
            {state === "succeeded" && <Check size={12} strokeWidth={3} />}
            {state === "failed" && <X size={12} strokeWidth={3} />}
          </span>
        );
      })}
      {/* <span className="attempt-count">
        {job.attempts}/{job.max_attempts}
      </span> */}
    </div>
  );
}

export function JobsPage() {
  const [jobs, setJobs] = useState<Job[]>([]);
  const [totalJobs, setTotalJobs] = useState(0);
  const [createOpen, setCreateOpen] = useState(false);
  const [statusFilter, setStatusFilter] = useState<JobStatus | "all">("all");
  const [typeFilter, setTypeFilter] = useState("");
  const [pageSize, setPageSize] = useState(25);
  const [currentPage, setCurrentPage] = useState(1);
  const [jobType, setJobType] = useState("demo.success");
  const [payload, setPayload] = useState(JSON.stringify(demoPayload("demo.success"), null, 2));
  const [priority, setPriority] = useState(50);
  const [maxAttempts, setMaxAttempts] = useState(3);
  const [idempotencyKey, setIdempotencyKey] = useState("");
  const [availableAt, setAvailableAt] = useState("");

  const refresh = useCallback(async () => {
    const page = await api.listJobsPage({
      limit: pageSize,
      offset: (currentPage - 1) * pageSize,
      status: statusFilter === "all" ? undefined : statusFilter,
      type: typeFilter
    });
    setJobs(page.results);
    setTotalJobs(page.count);
  }, [currentPage, pageSize, statusFilter, typeFilter]);

  useEffect(() => {
    void refresh().catch((caught) => toast.error(caught instanceof Error ? caught.message : String(caught)));
    const id = window.setInterval(() => void refresh().catch(() => undefined), 2000);
    return () => window.clearInterval(id);
  }, [refresh]);

  useEffect(() => {
    setCurrentPage(1);
  }, [pageSize, statusFilter, typeFilter]);

  const totalPages = Math.max(1, Math.ceil(totalJobs / pageSize));
  const firstItem = totalJobs ? (currentPage - 1) * pageSize + 1 : 0;
  const lastItem = Math.min(totalJobs, currentPage * pageSize);

  useEffect(() => {
    if (currentPage > totalPages) setCurrentPage(totalPages);
  }, [currentPage, totalPages]);

  function setType(nextType: string) {
    setJobType(nextType);
    setPayload(JSON.stringify(demoPayload(nextType), null, 2));
  }

  async function createJob(event: FormEvent) {
    event.preventDefault();
    try {
      await api.createJob({
        type: jobType,
        payload: parseJson(payload),
        priority,
        max_attempts: maxAttempts,
        available_at: availableAt ? new Date(availableAt).toISOString() : null,
        idempotency_key: idempotencyKey || null
      });
      setCreateOpen(false);
      setIdempotencyKey("");
      await refresh();
      toast.success("Job created.");
    } catch (caught) {
      toast.error(caught instanceof Error ? caught.message : String(caught));
    }
  }

  async function createDemo(type: string, nextPriority = 50, nextMaxAttempts = 3) {
    try {
      await api.createJob({
        type,
        payload: demoPayload(type),
        priority: nextPriority,
        max_attempts: nextMaxAttempts
      });
      await refresh();
      setCreateOpen(false);
      toast.success("Demo job created.");
    } catch (caught) {
      toast.error(caught instanceof Error ? caught.message : String(caught));
    }
  }

  async function createBatch() {
    const batch = [
      ["demo.success", 90],
      ["demo.slow", 80],
      ["demo.flaky", 75],
      ["demo.fail", 60],
      ["demo.success", 55],
      ["demo.timeout", 50],
      ["demo.success", 45],
      ["demo.flaky", 40],
      ["demo.fail", 35],
      ["demo.success", 30]
    ] as const;
    try {
      await Promise.all(batch.map(([type, nextPriority]) => api.createJob({ type, payload: demoPayload(type), priority: nextPriority, max_attempts: 3 })));
      await refresh();
      setCreateOpen(false);
      toast.success("Batch created.");
    } catch (caught) {
      toast.error(caught instanceof Error ? caught.message : String(caught));
    }
  }

  const statusOptions: SelectOption[] = [{ value: "all", label: "All statuses" }, ...STATUSES.map((status) => ({ value: status, label: status }))];

  return (
    <div className="page">
      <header className="page-header">
        <div>
          <span className="eyebrow">Lifecycle</span>
          <h2>Jobs</h2>
        </div>
        <button type="button" onClick={() => setCreateOpen(true)}>
          <Plus size={16} /> Create Job
        </button>
      </header>

      <section className="panel">
        <div className="toolbar">
          <SelectField value={statusFilter} options={statusOptions} onChange={(value) => setStatusFilter(value as JobStatus | "all")} />
          <input placeholder="Filter by type" value={typeFilter} onChange={(event) => setTypeFilter(event.target.value)} />
        </div>
        <div className="table-wrap">
          <table>
            <thead>
              <tr>
                <th>Status</th>
                <th>Type</th>
                <th>Priority</th>
                <th>Attempts</th>
                <th>Available</th>
                <th>Locked By</th>
                <th>Action</th>
              </tr>
            </thead>
            <tbody>
              {jobs.map((job) => (
                <tr key={job.id}>
                  <td>
                    <StatusBadge status={job.status} />
                  </td>
                  <td>
                    <code className="inline-code">{job.type}</code>
                  </td>
                  <td>{job.priority}</td>
                  <td>
                    <AttemptMeter job={job} />
                  </td>
                  <td>
                    <DateTime value={job.available_at} />
                  </td>
                  <td>{job.locked_by ?? "-"}</td>
                  <td>
                    <Link className="icon-button" aria-label={`view ${job.type}`} title="View job" to={`/jobs/${job.id}`}>
                      <Eye size={18} />
                    </Link>
                  </td>
                </tr>
              ))}
            </tbody>
          </table>
          {!jobs.length && <EmptyState title="No jobs match the current filter" />}
        </div>
        <div className="pagination-bar">
          <span>
            Showing {firstItem}-{lastItem} of {totalJobs}
          </span>
          <div className="pagination-controls">
            <SelectField value={String(pageSize)} options={pageSizeOptions} onChange={(value) => setPageSize(Number(value))} />
            <button className="secondary-button" disabled={currentPage <= 1} type="button" onClick={() => setCurrentPage((page) => Math.max(1, page - 1))}>
              <ChevronLeft size={16} /> Previous
            </button>
            <button
              className="secondary-button"
              disabled={currentPage >= totalPages}
              type="button"
              onClick={() => setCurrentPage((page) => Math.min(totalPages, page + 1))}
            >
              Next <ChevronRight size={16} />
            </button>
          </div>
        </div>
      </section>

      {createOpen && (
        <Modal title="Create Job" onClose={() => setCreateOpen(false)}>
          <form className="modal-form" onSubmit={createJob}>
            <label>Quick Create</label>
            <div className="preset-grid">
              <button className="secondary-button" type="button" onClick={() => setType("demo.success")}>
                Success
              </button>
              <button className="secondary-button" type="button" onClick={() => setType("demo.fail")}>
                Failing
              </button>
              <button className="secondary-button" type="button" onClick={() => setType("demo.slow")}>
                Slow
              </button>
              <button className="secondary-button" type="button" onClick={() => setType("demo.timeout")}>
                Timeout
              </button>
              <button className="secondary-button" type="button" onClick={() => setType("demo.flaky")}>
                Flaky Retry
              </button>
              <button className="primary-button" type="button" onClick={() => void createBatch()}>
                <RotateCcw size={16} /> 10 Mixed
              </button>
            </div>
            <SelectField label="Job type" value={jobType} options={jobTypeOptions} onChange={setType} />
            <JsonField label="Payload JSON" value={payload} onChange={setPayload} />
            <label>
              Priority
              <input type="number" value={priority} onChange={(event) => setPriority(Number(event.target.value))} />
            </label>
            <label>
              Max attempts
              <input min={1} type="number" value={maxAttempts} onChange={(event) => setMaxAttempts(Number(event.target.value))} />
            </label>
            <label>
              Available at
              <input type="datetime-local" value={availableAt} onChange={(event) => setAvailableAt(event.target.value)} />
            </label>
            <label>
              Idempotency key
              <input value={idempotencyKey} onChange={(event) => setIdempotencyKey(event.target.value)} />
            </label>
            <div className="modal-actions">
              <button className="secondary-button" type="button" onClick={() => setCreateOpen(false)}>
                Cancel
              </button>
              <button type="submit">Create</button>
            </div>
          </form>
        </Modal>
      )}
    </div>
  );
}
