import { useEffect, useMemo, useRef, useState } from 'react';

import {
  ATTEMPT_STATUSES,
  JOB_ITEM_TERMINAL_STATES,
  JOB_STATUSES,
  MAX_PAGE_SIZE,
  type Attempt,
  type Job,
  type JobItem,
  type JobPage,
  type JobPageQuery,
  type JobStatus,
  type SortDirection,
} from '@multi-simadmin/contracts';

const PAGE_SIZE = 25;
const MAX_STRING = 256;
const TERMINAL_ITEMS = new Set<string>(JOB_ITEM_TERMINAL_STATES);
const JOB_STATUS_SET = new Set<string>(JOB_STATUSES);
const ATTEMPT_STATUS_SET = new Set<string>(ATTEMPT_STATUSES);

type SafeProblem = Pick<NonNullable<JobItem['error']>, 'code' | 'title' | 'status'>;
type SafeItem = Omit<JobItem, 'error'> & { readonly error?: SafeProblem };
type SafeJob = Omit<Job, 'items'> & { readonly items: readonly SafeItem[] };
export interface SafeJobPage {
  readonly items: readonly SafeJob[];
  readonly page: JobPage['page'];
}

export interface JobsDataSource {
  load(query: JobPageQuery, signal: AbortSignal): Promise<unknown>;
}
export interface JobsPageProps {
  readonly dataSource?: JobsDataSource;
  readonly refreshSignal?: number;
}

type SortField = NonNullable<JobPageQuery['sort']>;

function record(value: unknown): Record<string, unknown> | null {
  return typeof value === 'object' && value !== null && !Array.isArray(value)
    ? (value as Record<string, unknown>)
    : null;
}
function boundedString(value: unknown): string | null {
  return typeof value === 'string' && value.length > 0 && value.length <= MAX_STRING ? value : null;
}
function timestamp(value: unknown): string | null {
  if (typeof value !== 'string' || value.length > MAX_STRING) return null;
  const time = Date.parse(value);
  if (!Number.isFinite(time)) return null;
  const canonical = new Date(time).toISOString();
  return canonical === value ? value : null;
}
function parseProblem(value: unknown): SafeProblem | undefined {
  const source = record(value);
  if (!source) return undefined;
  const code = boundedString(source.code);
  const title = boundedString(source.title);
  if (
    !code ||
    !title ||
    typeof source.status !== 'number' ||
    !Number.isInteger(source.status) ||
    source.status < 100 ||
    source.status > 599
  )
    return undefined;
  return { code, title, status: source.status };
}
function hasContractProblem(value: unknown): boolean {
  const source = record(value);
  return Boolean(
    source &&
    boundedString(source.type) &&
    boundedString(source.detail) &&
    boundedString(source.requestId) &&
    parseProblem(source),
  );
}
function parseItem(value: unknown): SafeItem | null {
  const source = record(value);
  if (!source) return null;
  const id = boundedString(source.id);
  const targetId = boundedString(source.targetId);
  const state =
    typeof source.state === 'string' && TERMINAL_ITEMS.has(source.state)
      ? (source.state as SafeItem['state'])
      : null;
  if (!id || !targetId || !state) return null;
  const sourceJobItemId = boundedString(source.sourceJobItemId);
  const error = parseProblem(source.error);
  if (source.sourceJobItemId !== undefined && !sourceJobItemId) return null;
  if (source.error !== undefined && (!error || !hasContractProblem(source.error))) return null;
  return {
    id,
    targetId,
    state,
    ...(sourceJobItemId ? { sourceJobItemId } : {}),
    ...(error ? { error } : {}),
  };
}
function parseAttempt(value: unknown): Attempt | null {
  const source = record(value);
  if (!source) return null;
  const id = boundedString(source.id);
  const startedAt = timestamp(source.startedAt);
  const state =
    typeof source.state === 'string' && ATTEMPT_STATUS_SET.has(source.state)
      ? (source.state as Attempt['state'])
      : null;
  if (!id || !startedAt || !state) return null;
  const finishedAt = timestamp(source.finishedAt);
  if (source.finishedAt !== undefined && !finishedAt) return null;
  return { id, state, startedAt, ...(finishedAt ? { finishedAt } : {}) };
}
function parseJob(value: unknown): SafeJob | null {
  const source = record(value);
  if (!source || !Array.isArray(source.items) || !Array.isArray(source.attempts)) return null;
  const id = boundedString(source.id);
  const operationId = boundedString(source.operationId);
  const rootJobId = boundedString(source.rootJobId);
  const createdAt = timestamp(source.createdAt);
  const status =
    typeof source.status === 'string' && JOB_STATUS_SET.has(source.status)
      ? (source.status as JobStatus)
      : null;
  if (!id || !operationId || !rootJobId || !createdAt || !status) return null;
  if (source.items.length > MAX_PAGE_SIZE || source.attempts.length > MAX_PAGE_SIZE) return null;
  const items = source.items.map(parseItem);
  const attempts = source.attempts.map(parseAttempt);
  if (items.some((item) => item === null) || attempts.some((attempt) => attempt === null))
    return null;
  const retryOfJobId = boundedString(source.retryOfJobId);
  if (source.retryOfJobId !== undefined && !retryOfJobId) return null;
  return {
    id,
    operationId,
    rootJobId,
    createdAt,
    status,
    items: items as SafeItem[],
    attempts: attempts as Attempt[],
    ...(retryOfJobId ? { retryOfJobId } : {}),
  };
}

/** Converts an untrusted transport response into the only shape retained by this UI. */
export function sanitizeJobPage(value: unknown): SafeJobPage | null {
  const source = record(value);
  if (!source || !Array.isArray(source.items)) return null;
  const page = record(source.page);
  if (!page) return null;
  if (
    source.items.length > MAX_PAGE_SIZE ||
    !Number.isSafeInteger(page.page) ||
    (page.page as number) < 1 ||
    !Number.isSafeInteger(page.pageSize) ||
    (page.pageSize as number) < 1 ||
    (page.pageSize as number) > MAX_PAGE_SIZE ||
    !Number.isSafeInteger(page.total) ||
    (page.total as number) < 0 ||
    source.items.length > (page.pageSize as number) ||
    source.items.length > (page.total as number)
  )
    return null;
  const items: SafeJob[] = [];
  for (const candidate of source.items) {
    const parsed = parseJob(candidate);
    if (!parsed) return null;
    items.push(parsed);
  }
  return {
    items,
    page: {
      page: page.page as number,
      pageSize: page.pageSize as number,
      total: page.total as number,
    },
  };
}

function statusLabel(value: string): string {
  return value
    .split('-')
    .map((part) => part[0]?.toUpperCase() + part.slice(1))
    .join(' ');
}
function safeLoadError(value: unknown): SafeProblem | null {
  return parseProblem(value) ?? null;
}

export function JobsPage({ dataSource, refreshSignal = 0 }: JobsPageProps) {
  const [result, setResult] = useState<SafeJobPage | null>(null);
  const [loading, setLoading] = useState(false);
  const [failed, setFailed] = useState<SafeProblem | true | null>(null);
  const [manualRefresh, setManualRefresh] = useState(0);
  const [status, setStatus] = useState<JobStatus | ''>('');
  const [operation, setOperation] = useState('');
  const [rootJob, setRootJob] = useState('');
  const [instance, setInstance] = useState('');
  const [sort, setSort] = useState<SortField>('createdAt');
  const [direction, setDirection] = useState<SortDirection>('desc');
  const [page, setPage] = useState(1);
  const request = useRef(0);

  const query = useMemo<JobPageQuery>(
    () => ({
      ...(status ? { status } : {}),
      ...(operation ? { operationId: operation } : {}),
      ...(rootJob ? { rootJobId: rootJob } : {}),
      ...(instance ? { instanceId: instance } : {}),
      sort,
      direction,
      page,
      pageSize: PAGE_SIZE,
    }),
    [direction, instance, operation, page, rootJob, sort, status],
  );

  useEffect(() => {
    if (!dataSource) return;
    const controller = new AbortController();
    const requestId = ++request.current;
    setLoading(true);
    setFailed(null);
    void dataSource.load(query, controller.signal).then(
      (raw) => {
        if (controller.signal.aborted || request.current !== requestId) return;
        const clean = sanitizeJobPage(raw);
        if (!clean) {
          setResult(null);
          setFailed(true);
        } else {
          setResult(clean);
        }
        setLoading(false);
      },
      (reason: unknown) => {
        if (controller.signal.aborted || request.current !== requestId) return;
        setResult(null);
        setFailed(safeLoadError(reason) ?? true);
        setLoading(false);
      },
    );
    return () => controller.abort();
  }, [dataSource, manualRefresh, query, refreshSignal]);

  if (!dataSource) {
    return (
      <section aria-labelledby="jobs-title">
        <h1 id="jobs-title">Jobs</h1>
        <p role="status" aria-label="Jobs unavailable">
          Jobs are runtime-unavailable because no jobs data source was provided.
        </p>
      </section>
    );
  }

  const total = result?.page.total ?? 0;
  const pageCount = Math.max(1, Math.ceil(total / PAGE_SIZE));
  const changeFilter = (change: () => void) => {
    change();
    setPage(1);
  };
  const changeSort = (field: SortField) => {
    if (sort === field) setDirection((current) => (current === 'asc' ? 'desc' : 'asc'));
    else {
      setSort(field);
      setDirection('asc');
    }
    setPage(1);
  };

  return (
    <section aria-labelledby="jobs-title">
      <header>
        <h1 id="jobs-title">Jobs</h1>
        <p>Read-only operation history.</p>
      </header>
      <div className="jobs-toolbar">
        <label>
          Status
          <select
            aria-label="Status"
            value={status}
            onChange={(event) =>
              changeFilter(() => setStatus(event.currentTarget.value as JobStatus | ''))
            }
          >
            <option value="">All statuses</option>
            {JOB_STATUSES.map((value) => (
              <option key={value} value={value}>
                {statusLabel(value)}
              </option>
            ))}
          </select>
        </label>
        <label>
          Operation
          <input
            aria-label="Operation"
            value={operation}
            onChange={(event) => changeFilter(() => setOperation(event.currentTarget.value))}
          />
        </label>
        <label>
          Root job
          <input
            aria-label="Root job"
            value={rootJob}
            onChange={(event) => changeFilter(() => setRootJob(event.currentTarget.value))}
          />
        </label>
        <label>
          Instance
          <input
            aria-label="Instance"
            value={instance}
            onChange={(event) => changeFilter(() => setInstance(event.currentTarget.value))}
          />
        </label>
        <button
          type="button"
          aria-label="Refresh jobs"
          onClick={() => setManualRefresh((value) => value + 1)}
        >
          Refresh
        </button>
      </div>
      {loading ? (
        <p role="status" aria-label="Jobs loading status">
          Loading jobs…
        </p>
      ) : null}
      {failed ? (
        <div role="alert">
          <p>
            Jobs could not be loaded.
            {failed !== true ? ` ${failed.code}: ${failed.title} (${failed.status})` : ''}
          </p>
        </div>
      ) : null}
      {!loading && !failed && result?.items.length === 0 ? (
        <p>No jobs match the current query.</p>
      ) : null}
      {!loading && !failed && result ? (
        <>
          <div className="table-scroll" role="region" aria-label="Jobs table" tabIndex={0}>
            <table className="dense-table" aria-label="Jobs">
              <thead>
                <tr>
                  <th
                    scope="col"
                    aria-sort={
                      sort === 'createdAt'
                        ? direction === 'asc'
                          ? 'ascending'
                          : 'descending'
                        : undefined
                    }
                  >
                    <button
                      type="button"
                      aria-label="Sort by created time"
                      onClick={() => changeSort('createdAt')}
                    >
                      Created
                    </button>
                  </th>
                  <th scope="col">Job</th>
                  <th
                    scope="col"
                    aria-sort={
                      sort === 'operationId'
                        ? direction === 'asc'
                          ? 'ascending'
                          : 'descending'
                        : undefined
                    }
                  >
                    <button
                      type="button"
                      aria-label="Sort by operation"
                      onClick={() => changeSort('operationId')}
                    >
                      Operation
                    </button>
                  </th>
                  <th
                    scope="col"
                    aria-sort={
                      sort === 'status'
                        ? direction === 'asc'
                          ? 'ascending'
                          : 'descending'
                        : undefined
                    }
                  >
                    <button
                      type="button"
                      aria-label="Sort by status"
                      onClick={() => changeSort('status')}
                    >
                      Status
                    </button>
                  </th>
                  <th scope="col">Root job</th>
                  <th scope="col">Items</th>
                  <th scope="col">Attempts</th>
                </tr>
              </thead>
              <tbody>
                {result.items.map((item) => (
                  <tr key={item.id}>
                    <td>{item.createdAt}</td>
                    <th scope="row">{item.id}</th>
                    <td>{item.operationId}</td>
                    <td>{statusLabel(item.status)}</td>
                    <td>{item.rootJobId}</td>
                    <td>
                      {item.items.length === 0 ? (
                        <span>
                          {item.status === 'queued' ||
                          item.status === 'running' ||
                          item.status === 'cancelling'
                            ? 'No terminal item results while this job is active.'
                            : 'No terminal item results.'}
                        </span>
                      ) : null}
                      {item.items.map((entry) => (
                        <div key={entry.id}>
                          <span>{entry.targetId}</span> — <span>{statusLabel(entry.state)}</span>
                          {entry.error ? (
                            <>
                              {' '}
                              — <span>{entry.error.code}</span> — <span>{entry.error.title}</span> —{' '}
                              <span>{entry.error.status}</span>
                            </>
                          ) : null}
                        </div>
                      ))}
                    </td>
                    <td>
                      {item.attempts.map((entry) => (
                        <div key={entry.id}>
                          {entry.id} — {statusLabel(entry.state)}
                        </div>
                      ))}
                    </td>
                  </tr>
                ))}
              </tbody>
            </table>
          </div>
          <nav aria-label="Jobs pagination">
            <button
              type="button"
              aria-label="Previous page"
              disabled={page <= 1}
              onClick={() => setPage((value) => value - 1)}
            >
              Previous
            </button>
            <span>
              Page {page} of {pageCount}
            </span>
            <button
              type="button"
              aria-label="Next page"
              disabled={page >= pageCount}
              onClick={() => setPage((value) => value + 1)}
            >
              Next
            </button>
          </nav>
        </>
      ) : null}
    </section>
  );
}
