import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { Button } from 'primereact/button';
import { Card } from 'primereact/card';
import { Toast } from 'primereact/toast';
import { Badge } from 'primereact/badge';
import { Tag } from 'primereact/tag';
import { ProgressBar } from 'primereact/progressbar';
import { api } from '../api/client';
import { useWebSocket } from '../hooks/useWebSocket';
import AudiobookUploadPanel from '../components/AudiobookUploadPanel';
import AudiobookConfigPanel from '../components/AudiobookConfigPanel';
import AudiobookOutputExplorer from '../components/AudiobookOutputExplorer';
import { getStatusLabel, getStatusSeverity, normalizeStatus } from '../utils/statusPresentation';
import { resolveMediaType } from '../utils/jobTaxonomy';
import { confirmModal } from '../utils/confirmModal';

const TERMINAL_STATES = new Set(['DONE', 'FINISHED']);

function normalizeJobId(value) {
  const parsed = Number(value);
  if (!Number.isFinite(parsed) || parsed <= 0) {
    return null;
  }
  return Math.trunc(parsed);
}

function parseEncodePlan(job) {
  if (job?.encodePlan && typeof job.encodePlan === 'object') {
    return job.encodePlan;
  }
  try {
    return JSON.parse(job?.encode_plan_json || '{}');
  } catch (_error) {
    return {};
  }
}

function resolveAudiobookMetadata(job, encodePlan) {
  const handbrakeMeta = job?.handbrakeInfo?.metadata && typeof job.handbrakeInfo.metadata === 'object'
    ? job.handbrakeInfo.metadata
    : {};
  const selectedMeta = job?.makemkvInfo?.selectedMetadata && typeof job.makemkvInfo.selectedMetadata === 'object'
    ? job.makemkvInfo.selectedMetadata
    : {};
  const fallbackMeta = encodePlan?.metadata && typeof encodePlan.metadata === 'object'
    ? encodePlan.metadata
    : {};
  const merged = {
    ...fallbackMeta,
    ...selectedMeta,
    ...handbrakeMeta
  };
  const chapters = Array.isArray(merged?.chapters)
    ? merged.chapters
    : (Array.isArray(encodePlan?.chapters) ? encodePlan.chapters : []);

  return {
    title: String(job?.title || job?.detected_title || merged?.title || '').trim() || null,
    author: String(merged?.author || merged?.artist || '').trim() || null,
    narrator: String(merged?.narrator || '').trim() || null,
    description: String(merged?.description || '').trim() || null,
    series: String(merged?.series || '').trim() || null,
    part: Number.isFinite(Number(merged?.part)) ? Math.trunc(Number(merged.part)) : null,
    year: Number.isFinite(Number(job?.year))
      ? Math.trunc(Number(job.year))
      : (Number.isFinite(Number(merged?.year)) ? Math.trunc(Number(merged.year)) : null),
    chapters,
    durationMs: Number.isFinite(Number(merged?.durationMs)) ? Number(merged.durationMs) : 0,
    poster: String(job?.poster_url || merged?.poster || '').trim() || null
  };
}

function buildAudiobookPipeline(job, progress = null) {
  const encodePlan = parseEncodePlan(job);
  const selectedMetadata = resolveAudiobookMetadata(job, encodePlan);
  const reviewData = job?.makemkvInfo?.selectedMetadata && typeof job.makemkvInfo.selectedMetadata === 'object'
    ? job.makemkvInfo.selectedMetadata
    : selectedMetadata;
  const state = String(progress?.state || job?.status || '').trim().toUpperCase() || 'UNKNOWN';
  return {
    state,
    activeJobId: Number(job?.id) || null,
    progress: Number.isFinite(Number(progress?.progress)) ? Number(progress.progress) : 0,
    eta: progress?.eta || null,
    statusText: progress?.statusText || null,
    context: {
      jobId: Number(job?.id) || null,
      mode: 'audiobook',
      mediaProfile: 'audiobook',
      selectedMetadata,
      audiobookConfig: {
        format: String(encodePlan?.format || job?.handbrakeInfo?.format || 'mp3').trim().toLowerCase() || 'mp3',
        formatOptions: encodePlan?.formatOptions && typeof encodePlan.formatOptions === 'object'
          ? encodePlan.formatOptions
          : {}
      },
      mediaInfoReview: reviewData,
      outputPath: String(job?.output_path || encodePlan?.outputPath || '').trim() || null,
      currentChapter: progress?.currentChapter && typeof progress.currentChapter === 'object'
        ? progress.currentChapter
        : null,
      completedChapterCount: Number.isFinite(Number(progress?.completedChapterCount))
        ? Number(progress.completedChapterCount)
        : null
    }
  };
}

export default function AudiobooksPage({
  audiobookUpload,
  onAudiobookUpload
}) {
  const toastRef = useRef(null);
  const navigate = useNavigate();
  const [jobs, setJobs] = useState([]);
  const [loadingJobs, setLoadingJobs] = useState(false);
  const [expandedJobId, setExpandedJobId] = useState(undefined);
  const [jobProgress, setJobProgress] = useState({});
  const [actionBusyJobIds, setActionBusyJobIds] = useState(() => new Set());
  const explorerRefreshToken = 0;

  const setActionBusy = (jobId, busy) => {
    const normalizedJobId = normalizeJobId(jobId);
    if (!normalizedJobId) {
      return;
    }
    setActionBusyJobIds((prev) => {
      const next = new Set(prev);
      if (busy) {
        next.add(normalizedJobId);
      } else {
        next.delete(normalizedJobId);
      }
      return next;
    });
  };

  const loadJobs = useCallback(async () => {
    setLoadingJobs(true);
    try {
      const response = await api.getAudiobookJobs();
      const rows = Array.isArray(response?.jobs) ? response.jobs : [];
      const audiobookRows = rows
        .filter((job) => resolveMediaType(job) === 'audiobook')
        .sort((left, right) => Number(right?.id || 0) - Number(left?.id || 0));
      setJobs(audiobookRows);
      setJobProgress((prev) => {
        const next = { ...prev };
        for (const job of audiobookRows) {
          const jobId = normalizeJobId(job?.id);
          if (!jobId) {
            continue;
          }
          const status = String(job?.status || '').trim().toUpperCase();
          if (!['ANALYZING', 'ENCODING'].includes(status)) {
            delete next[jobId];
          }
        }
        return next;
      });
    } catch (error) {
      console.error('AudiobooksPage load jobs error:', error);
    } finally {
      setLoadingJobs(false);
    }
  }, []);

  useEffect(() => {
    void loadJobs();
    const intervalId = setInterval(() => void loadJobs(), 5000);
    return () => clearInterval(intervalId);
  }, [loadJobs]);

  useWebSocket({
    onMessage: (message) => {
      if (!message?.type || !message?.payload) {
        return;
      }
      if (message.type === 'PIPELINE_PROGRESS') {
        const payload = message.payload;
        const jobId = normalizeJobId(payload?.activeJobId);
        if (!jobId) {
          return;
        }
        setJobProgress((prev) => ({
          ...prev,
          [jobId]: {
            progress: payload?.progress ?? null,
            eta: payload?.eta ?? null,
            statusText: payload?.statusText ?? null,
            state: payload?.state ?? null,
            currentChapter: payload?.contextPatch?.currentChapter ?? null,
            completedChapterCount: payload?.contextPatch?.completedChapterCount ?? null
          }
        }));
      }
      if (
        message.type === 'PIPELINE_UPDATE'
        || message.type === 'PIPELINE_STATE_CHANGED'
        || message.type === 'PIPELINE_QUEUE_CHANGED'
      ) {
        void loadJobs();
      }
    }
  });

  const activeJobs = useMemo(
    () => jobs.filter((job) => !TERMINAL_STATES.has(String(job?.status || '').trim().toUpperCase())),
    [jobs]
  );

  useEffect(() => {
    const normalizedExpanded = normalizeJobId(expandedJobId);
    const hasExpanded = activeJobs.some((job) => normalizeJobId(job?.id) === normalizedExpanded);
    if (hasExpanded) {
      return;
    }
    if (expandedJobId === null) {
      return;
    }
    if (activeJobs.length === 0) {
      return;
    }
    setExpandedJobId(normalizeJobId(activeJobs[0]?.id));
  }, [activeJobs, expandedJobId]);

  const handleAudiobookStart = async (jobId, audiobookConfig) => {
    const normalizedJobId = normalizeJobId(jobId);
    if (!normalizedJobId) {
      return;
    }
    setActionBusy(normalizedJobId, true);
    try {
      const response = await api.startAudiobook(normalizedJobId, audiobookConfig || {});
      const queued = Boolean(response?.result?.queued);
      toastRef.current?.show({
        severity: queued ? 'info' : 'success',
        summary: queued ? 'Job eingereiht' : 'Audiobook gestartet',
        detail: queued
          ? `Job #${normalizedJobId} wurde in die Warteschlange eingereiht.`
          : `Job #${normalizedJobId} wurde gestartet.`,
        life: 3200
      });
      await loadJobs();
    } catch (error) {
      toastRef.current?.show({
        severity: 'error',
        summary: 'Start fehlgeschlagen',
        detail: error?.message || 'Bitte Logs pruefen.',
        life: 4200
      });
    } finally {
      setActionBusy(normalizedJobId, false);
    }
  };

  const handleCancel = async (jobId) => {
    const normalizedJobId = normalizeJobId(jobId);
    if (!normalizedJobId) {
      return;
    }
    setActionBusy(normalizedJobId, true);
    try {
      await api.cancelJob(normalizedJobId);
      toastRef.current?.show({
        severity: 'info',
        summary: 'Abbruch angefordert',
        detail: `Job #${normalizedJobId} wird abgebrochen.`,
        life: 2600
      });
      await loadJobs();
    } catch (error) {
      toastRef.current?.show({
        severity: 'error',
        summary: 'Abbruch fehlgeschlagen',
        detail: error?.message || 'Bitte Logs pruefen.',
        life: 4200
      });
    } finally {
      setActionBusy(normalizedJobId, false);
    }
  };

  const handleRetry = async (jobId) => {
    const normalizedJobId = normalizeJobId(jobId);
    if (!normalizedJobId) {
      return;
    }
    setActionBusy(normalizedJobId, true);
    try {
      const response = await api.retryJob(normalizedJobId);
      const retryJobId = normalizeJobId(response?.retryJob?.id);
      toastRef.current?.show({
        severity: 'success',
        summary: 'Retry erstellt',
        detail: retryJobId
          ? `Neuer Job #${retryJobId} wurde angelegt.`
          : `Retry fuer Job #${normalizedJobId} wurde angelegt.`,
        life: 3200
      });
      await loadJobs();
      if (retryJobId) {
        setExpandedJobId(retryJobId);
      }
    } catch (error) {
      toastRef.current?.show({
        severity: 'error',
        summary: 'Retry fehlgeschlagen',
        detail: error?.message || 'Bitte Logs pruefen.',
        life: 4200
      });
    } finally {
      setActionBusy(normalizedJobId, false);
    }
  };

  const handleDelete = async (jobId) => {
    const normalizedJobId = normalizeJobId(jobId);
    if (!normalizedJobId) {
      return;
    }
    const confirmed = await confirmModal({
      message: `Job #${normalizedJobId} wirklich aus der Historie entfernen?`,
      header: 'Job loeschen',
      icon: 'pi pi-exclamation-triangle',
      acceptClassName: 'p-button-danger',
      acceptLabel: 'Loeschen',
      rejectLabel: 'Abbrechen'
    });
    if (!confirmed) {
      return;
    }

    setActionBusy(normalizedJobId, true);
    try {
      await api.deleteJobEntry(normalizedJobId, 'none', { includeRelated: false });
      toastRef.current?.show({
        severity: 'success',
        summary: 'Job geloescht',
        detail: `Job #${normalizedJobId} wurde entfernt.`,
        life: 2800
      });
      if (normalizeJobId(expandedJobId) === normalizedJobId) {
        setExpandedJobId(null);
      }
      await loadJobs();
    } catch (error) {
      toastRef.current?.show({
        severity: 'error',
        summary: 'Loeschen fehlgeschlagen',
        detail: error?.message || 'Bitte Logs pruefen.',
        life: 4200
      });
    } finally {
      setActionBusy(normalizedJobId, false);
    }
  };

  const handleUploaded = async (uploadedJobId) => {
    const normalizedJobId = normalizeJobId(uploadedJobId);
    await loadJobs();
    if (normalizedJobId) {
      setExpandedJobId(normalizedJobId);
    }
  };

  const jobsCardHeader = (
    <div className="converter-card-header">
      <div className="converter-card-title">
        <span>Audiobook Jobs</span>
        {activeJobs.length > 0 ? (
          <Badge value={activeJobs.length} severity="info" />
        ) : null}
      </div>
      <Button
        icon={loadingJobs ? 'pi pi-spin pi-spinner' : 'pi pi-refresh'}
        text
        rounded
        size="small"
        onClick={() => void loadJobs()}
        disabled={loadingJobs}
        aria-label="Jobs neu laden"
      />
    </div>
  );

  return (
    <div className="ripper-subpage-content">
      <Toast ref={toastRef} position="top-right" />

      <Card title="Audiobooks Upload" subTitle="AAX-Dateien importieren und als Audiobook-Job vorbereiten">
        <AudiobookUploadPanel
          audiobookUpload={audiobookUpload}
          onAudiobookUpload={onAudiobookUpload}
          onUploaded={handleUploaded}
        />
      </Card>

      <Card header={jobsCardHeader}>
        {activeJobs.length === 0 ? (
          <p className="converter-jobs-empty-hint">
            <small>Keine aktiven Audiobook-Jobs vorhanden. Fertige Jobs findest du in der Historie.</small>
          </p>
        ) : (
          <div className="ripper-job-list">
            {activeJobs.map((job) => {
              const jobId = normalizeJobId(job?.id);
              if (!jobId) {
                return null;
              }
              const pipelineForJob = buildAudiobookPipeline(job, jobProgress[jobId] || null);
              const state = String(pipelineForJob?.state || job?.status || '').trim().toUpperCase();
              const isExpanded = normalizeJobId(expandedJobId) === jobId;
              const busy = actionBusyJobIds.has(jobId);
              const title = String(job?.title || job?.detected_title || `Job #${jobId}`).trim();
              const progressValue = Number.isFinite(Number(pipelineForJob?.progress))
                ? Math.max(0, Math.min(100, Number(pipelineForJob.progress)))
                : 0;
              const showProgress = ['ANALYZING', 'ENCODING'].includes(state) && progressValue > 0;

              if (!isExpanded) {
                return (
                  <button
                    key={jobId}
                    type="button"
                    className="ripper-job-row"
                    onClick={() => setExpandedJobId(jobId)}
                  >
                    <div className="poster-thumb ripper-job-poster-fallback">Kein Cover</div>
                    <div className="ripper-job-row-content">
                      <div className="ripper-job-row-main">
                        <strong className="ripper-job-title-line">
                          <i className="pi pi-headphones media-indicator-icon" style={{ fontSize: '1rem', color: 'var(--rip-muted)' }} />
                          <span>{title}</span>
                        </strong>
                        <small>#{jobId}</small>
                      </div>
                      <div className="ripper-job-badges">
                        <Tag value={getStatusLabel(state)} severity={getStatusSeverity(normalizeStatus(state))} />
                        <Tag value="Audiobook" severity="info" />
                      </div>
                      {showProgress ? (
                        <div className="ripper-job-row-progress">
                          <ProgressBar value={progressValue} showValue={false} />
                        </div>
                      ) : null}
                    </div>
                  </button>
                );
              }

              return (
                <div key={jobId} className="ripper-job-expanded">
                  <div className="ripper-job-expanded-head">
                    <div className="poster-thumb ripper-job-poster-fallback">Kein Cover</div>
                    <div className="ripper-job-expanded-title">
                      <strong className="ripper-job-title-line">
                        <i className="pi pi-headphones media-indicator-icon" style={{ fontSize: '1rem', color: 'var(--rip-muted)' }} />
                        <span>#{jobId} | {title}</span>
                      </strong>
                      <div className="ripper-job-badges">
                        <Tag value={getStatusLabel(state)} severity={getStatusSeverity(normalizeStatus(state))} />
                        <Tag value="Audiobook" severity="info" />
                      </div>
                    </div>
                    <div style={{ display: 'flex', gap: '0.5rem' }}>
                      <Button
                        label="Im Ripper"
                        icon="pi pi-arrow-right"
                        severity="secondary"
                        outlined
                        onClick={() => navigate('/ripper')}
                      />
                      <Button
                        label="Einklappen"
                        icon="pi pi-angle-up"
                        severity="secondary"
                        outlined
                        onClick={() => setExpandedJobId(null)}
                        disabled={busy}
                      />
                    </div>
                  </div>
                  <AudiobookConfigPanel
                    pipeline={pipelineForJob}
                    onStart={(config) => void handleAudiobookStart(jobId, config)}
                    onCancel={() => void handleCancel(jobId)}
                    onRetry={() => void handleRetry(jobId)}
                    onDeleteJob={() => void handleDelete(jobId)}
                    busy={busy}
                  />
                </div>
              );
            })}
          </div>
        )}
      </Card>

      <Card
        title="Audiobook Output Explorer"
        subTitle="Read-only Explorer auf dem Audiobook-Ausgabeordner"
      >
        <AudiobookOutputExplorer refreshToken={explorerRefreshToken} />
      </Card>
    </div>
  );
}
