import { Op } from 'sequelize';
import sequelize from '../database.js';
import {
  CalendarEvent,
  ClubCommunicationRecipient,
  ClubCommunicationThread,
  ClubDocument,
  ClubInvoiceParty,
  ClubInvoice,
  ClubPaymentClaim,
  ClubRequest,
  ClubSepaMandate,
  ClubTask,
  ClubTaskSuppression,
  Member,
} from '../models/index.js';
import { CLUB_TASK_DEFINITIONS, CLUB_WORKFLOW_SOURCES, getClubTaskDefinitionMap } from './clubTaskDefinitions.js';
import { hasClubPaymentClaimPaidAmountCentsColumn } from './clubPaymentClaimCompatibility.js';

const definitionMap = getClubTaskDefinitionMap();

async function loadAvailableTables() {
  const tables = await sequelize.getQueryInterface().showAllTables();
  return new Set(
    tables
      .map((table) => (typeof table === 'string' ? table : Object.values(table || {})[0]))
      .filter(Boolean)
      .map((table) => String(table).toLowerCase())
  );
}

function hasTable(availableTables, tableName) {
  return availableTables.has(String(tableName).toLowerCase());
}

function activeTask(task) {
  return !['done', 'cancelled', 'archived'].includes(task.status);
}

function buildAutomationKey(taskType, entityType, entityId, suffix = '') {
  return [taskType, entityType, entityId, suffix].filter(Boolean).join(':');
}

function todayStart() {
  const date = new Date();
  date.setHours(0, 0, 0, 0);
  return date;
}

function addDays(baseDate, days) {
  const date = new Date(baseDate);
  date.setDate(date.getDate() + days);
  return date;
}

function daysUntil(targetDate, today) {
  return Math.floor((targetDate.getTime() - today.getTime()) / 86400000);
}

function derivePriority(days) {
  if (days < 0) return 'urgent';
  if (days <= 2) return 'high';
  if (days <= 7) return 'normal';
  return 'low';
}

function formatReminderStage(reminderLevel) {
  const level = Math.max(0, Number.parseInt(reminderLevel, 10) || 0);
  if (level <= 0) return 'keine Mahnung';
  if (level === 1) return '1. Mahnung';
  if (level === 2) return '2. Mahnung';
  return 'letzte Mahnung';
}

function personName(entity) {
  return [entity?.firstName, entity?.lastName].filter(Boolean).join(' ').trim() || entity?.email || 'Unbekannt';
}

function wrapSuggestion(suggestion) {
  return {
    ...suggestion,
    definition: definitionMap[suggestion.taskType] || null,
  };
}

function suggestionToken(parts = []) {
  return parts
    .map((part) => (part == null ? '' : String(part).trim()))
    .join('|');
}

function hasExistingSourceTask(tasks, sourceEntityType, sourceEntityId, automationSource) {
  return tasks.some((task) =>
    task.relatedEntityType === sourceEntityType &&
    Number(task.relatedEntityId) === Number(sourceEntityId) &&
    task.automationSource === automationSource
  );
}

function requestSuggestionFor(request, today) {
  const requestDate = new Date(request.receivedAt || today);
  const name = personName(request);

  if (request.requestType === 'trial_training') {
    return {
      taskType: 'request_schedule_trial_training',
      title: `Probetraining für ${name} organisieren`,
      description: 'Anfrage prüfen, Trainingsgruppe auswählen und einen passenden Termin abstimmen.',
      priority: 'high',
      dueAt: addDays(requestDate, 2),
    };
  }

  if (request.requestType === 'membership') {
    return {
      taskType: 'request_membership_review',
      title: `Mitgliedsanfrage von ${name} prüfen`,
      description: 'Unterlagen, Rückfragen und Aufnahmeentscheidung im Vereinskontext bearbeiten.',
      priority: 'high',
      dueAt: addDays(requestDate, 3),
    };
  }

  if (request.requestType === 'sponsoring') {
    return {
      taskType: 'request_sponsoring_reply',
      title: `Sponsoringanfrage von ${name} nachfassen`,
      description: 'Sponsoringanfrage bewerten und nächsten Vereinskontakt vorbereiten.',
      priority: 'normal',
      dueAt: addDays(requestDate, 4),
    };
  }

  return {
    taskType: 'request_contact_reply',
    title: `Kontaktanfrage von ${name} beantworten`,
    description: 'Rückmeldung an die anfragende Person geben und Zuständigkeit festlegen.',
    priority: 'normal',
    dueAt: addDays(requestDate, 3),
  };
}

function documentSuggestionFor(document, today) {
  const updatedAt = document.updatedAt ? new Date(document.updatedAt) : today;
  const ageDays = Math.floor((today.getTime() - updatedAt.getTime()) / 86400000);
  const type = String(document.documentType || '').toLowerCase();
  const status = String(document.status || '').toLowerCase();

  if (!['satzung', 'protokoll', 'nachweis'].includes(type)) {
    return null;
  }

  if (status === 'archived' || status === 'obsolete') {
    return null;
  }

  const reviewThresholdDays = {
    satzung: 365,
    protokoll: 90,
    nachweis: 30,
  }[type] || 90;

  if (ageDays < reviewThresholdDays && status !== 'draft') {
    return null;
  }

  return {
    taskType: 'document_review_required',
    title: `${type === 'satzung' ? 'Satzung' : type === 'protokoll' ? 'Protokoll' : 'Nachweis'} prüfen: ${document.title}`,
    description: 'Wichtige Vereinsdokumente vor Freigabe oder Archivierung prüfen und den aktuellen Stand bestätigen.',
    priority: type === 'satzung' ? 'high' : 'normal',
    dueAt: addDays(updatedAt, Math.min(14, reviewThresholdDays)),
  };
}

function sponsorPartySuggestionFor(party, today) {
  const status = String(party.status || '').toLowerCase();
  const validTo = party.validTo ? new Date(party.validTo) : null;
  const daysLeft = validTo ? daysUntil(validTo, today) : null;

  if (status !== 'active' || !validTo || Number.isNaN(validTo.getTime())) {
    return null;
  }

  if (daysLeft > 60) {
    return null;
  }

  return {
    taskType: 'sponsor_contract_renewal',
    title: `Sponsoringvertrag von ${party.name} verlängern`,
    description: daysLeft <= 0
      ? 'Der Sponsorvertrag ist bereits abgelaufen und sollte umgehend geklärt werden.'
      : `Der Sponsorvertrag läuft in ${daysLeft} Tagen aus und sollte rechtzeitig verlängert werden.`,
    priority: daysLeft <= 14 ? 'high' : 'normal',
    dueAt: validTo,
  };
}

class ClubTaskAutomationService {
  async buildAutomationOverview(clubId) {
    const today = todayStart();
    const availableTables = await loadAvailableTables();
    const hasPaidAmountCentsColumn = await hasClubPaymentClaimPaidAmountCentsColumn();
    const [currentTasks, requests, members, mandates, paymentClaims, invoices, parties, documents, communicationRecipients, events, suppressions] = await Promise.all([
      ClubTask.findAll({
        where: {
          clubId,
          automationKey: { [Op.ne]: null },
        },
      }),
      hasTable(availableTables, 'club_requests')
        ? ClubRequest.findAll({
          where: {
            clubId,
            status: { [Op.in]: ['open', 'in_progress', 'waiting'] },
          },
          order: [['receivedAt', 'ASC']],
        })
        : Promise.resolve([]),
      Member.findAll({
        where: {
          clubId,
          active: true,
        },
        order: [['lastName', 'ASC'], ['firstName', 'ASC']],
      }),
      hasTable(availableTables, 'club_sepa_mandates')
        ? ClubSepaMandate.findAll({
          where: {
            clubId,
            status: 'active',
            revokedAt: null,
            memberId: { [Op.ne]: null },
          },
          attributes: ['memberId'],
        })
        : Promise.resolve([]),
      hasPaidAmountCentsColumn && hasTable(availableTables, 'club_payment_claims')
        ? ClubPaymentClaim.findAll({
          where: {
            clubId,
            status: { [Op.in]: ['open', 'partially_paid'] },
            archivedAt: null,
          },
          order: [['dueOn', 'ASC']],
        })
        : Promise.resolve([]),
      hasTable(availableTables, 'club_invoices')
        ? ClubInvoice.findAll({
          where: {
            clubId,
            status: { [Op.in]: ['issued', 'partially_paid'] },
            archivedAt: null,
            dueOn: { [Op.ne]: null },
          },
          order: [['dueOn', 'ASC']],
        })
        : Promise.resolve([]),
      hasTable(availableTables, 'club_invoice_parties')
        ? ClubInvoiceParty.findAll({
          where: {
            clubId,
            partyType: 'sponsor',
          },
          order: [['status', 'ASC'], ['validTo', 'ASC'], ['name', 'ASC']],
        })
        : Promise.resolve([]),
      hasTable(availableTables, 'club_documents')
        ? ClubDocument.findAll({
          where: {
            clubId,
            documentType: { [Op.in]: ['satzung', 'protokoll', 'nachweis'] },
            status: { [Op.in]: ['active', 'draft'] },
          },
          order: [['updatedAt', 'DESC']],
        })
        : Promise.resolve([]),
      hasTable(availableTables, 'club_communication_recipients') && hasTable(availableTables, 'club_communication_threads')
        ? ClubCommunicationRecipient.findAll({
          where: {
            clubId,
            deliveryStatus: 'failed',
            retryable: true,
          },
          include: [
            { model: ClubCommunicationThread, as: 'thread', required: false },
          ],
          order: [['updatedAt', 'DESC']],
        })
        : Promise.resolve([]),
      hasTable(availableTables, 'calendar_events')
        ? CalendarEvent.findAll({
          where: {
            clubId,
            endDate: { [Op.gte]: today.toISOString().slice(0, 10) },
          },
          order: [['startDate', 'ASC']],
          limit: 20,
        })
        : Promise.resolve([]),
      hasTable(availableTables, 'club_task_suppressions')
        ? ClubTaskSuppression.findAll({
          where: { clubId },
          attributes: ['automationKey', 'suppressionToken'],
        })
        : Promise.resolve([]),
    ]);

    const existingKeys = new Set(currentTasks.filter(activeTask).map((task) => task.automationKey).filter(Boolean));
    const memberIdsWithMandate = new Set(mandates.map((mandate) => Number(mandate.memberId)).filter(Boolean));
    const suppressionMap = new Map(
      suppressions
        .filter((entry) => entry?.automationKey && entry?.suppressionToken)
        .map((entry) => [String(entry.automationKey), String(entry.suppressionToken)])
    );
    const suggestions = [];

    const pushSuggestion = (suggestion) => {
      const key = String(suggestion.automationKey || '');
      const token = String(suggestion.suppressionToken || '');
      if (key && token && suppressionMap.get(key) === token) {
        return;
      }
      suggestions.push(wrapSuggestion(suggestion));
    };

    for (const request of requests) {
      const requestSuggestion = requestSuggestionFor(request, today);
      if (hasExistingSourceTask(currentTasks, 'club_request', request.id, 'club_requests')) continue;
      const key = buildAutomationKey(requestSuggestion.taskType, 'club_request', request.id);
      if (existingKeys.has(key)) continue;
      pushSuggestion({
        ...requestSuggestion,
        automationSource: 'club_requests',
        automationKey: key,
        suppressionToken: suggestionToken([
          request.updatedAt,
          request.status,
          request.workflowStage,
          request.convertedMemberId
        ]),
        sourceEntityType: 'club_request',
        sourceEntityId: request.id,
        sourceSnapshot: {
          requestType: request.requestType,
          status: request.status,
          subject: request.subject || null,
          person: personName(request),
          convertedMemberId: request.convertedMemberId || null,
        },
      });
    }

    for (const document of documents) {
      const suggestion = documentSuggestionFor(document, today);
      if (!suggestion) continue;
      const key = buildAutomationKey(suggestion.taskType, 'club_document', document.id);
      if (existingKeys.has(key)) continue;
      pushSuggestion({
        ...suggestion,
        automationSource: 'club_documents',
        automationKey: key,
        suppressionToken: suggestionToken([document.updatedAt, document.status, document.currentVersionNo, document.title]),
        sourceEntityType: 'club_document',
        sourceEntityId: document.id,
        sourceSnapshot: {
          documentType: document.documentType,
          status: document.status,
          title: document.title,
          currentVersionNo: document.currentVersionNo,
          archivedAt: document.archivedAt || null,
        },
      });
    }

    for (const party of parties) {
      const suggestion = sponsorPartySuggestionFor(party, today);
      if (!suggestion) continue;
      const key = buildAutomationKey(suggestion.taskType, 'club_invoice_party', party.id);
      if (existingKeys.has(key)) continue;
      pushSuggestion({
        ...suggestion,
        automationSource: 'club_invoice_parties',
        automationKey: key,
        suppressionToken: suggestionToken([party.updatedAt, party.status, party.validTo, party.contractReference]),
        sourceEntityType: 'club_invoice_party',
        sourceEntityId: party.id,
        sourceSnapshot: {
          partyName: party.name,
          status: party.status,
          contractReference: party.contractReference || null,
          validTo: party.validTo || null,
        },
      });
    }

    for (const member of members) {
      const name = personName(member);
      if (!String(member.email || '').trim()) {
        const key = buildAutomationKey('member_missing_email', 'member', member.id, 'email');
        if (!existingKeys.has(key)) {
          pushSuggestion({
            taskType: 'member_missing_email',
            title: `E-Mail für ${name} ergänzen`,
            description: 'Im Mitgliedsdatensatz fehlt eine E-Mail-Adresse.',
            priority: 'normal',
            dueAt: addDays(today, 7),
            automationSource: 'members',
            automationKey: key,
            suppressionToken: suggestionToken([member.updatedAt, 'email']),
            sourceEntityType: 'member',
            sourceEntityId: member.id,
            sourceSnapshot: { memberName: name, missingField: 'email' },
          });
        }
      }

      if (!String(member.birthDate || '').trim()) {
        const key = buildAutomationKey('member_missing_birthdate', 'member', member.id, 'birthdate');
        if (!existingKeys.has(key)) {
          pushSuggestion({
            taskType: 'member_missing_birthdate',
            title: `Geburtsdatum für ${name} ergänzen`,
            description: 'Im Mitgliedsdatensatz fehlt das Geburtsdatum.',
            priority: 'normal',
            dueAt: addDays(today, 7),
            automationSource: 'members',
            automationKey: key,
            suppressionToken: suggestionToken([member.updatedAt, 'birthdate']),
            sourceEntityType: 'member',
            sourceEntityId: member.id,
            sourceSnapshot: { memberName: name, missingField: 'birthdate' },
          });
        }
      }

      if (!memberIdsWithMandate.has(Number(member.id))) {
        const key = buildAutomationKey('member_missing_sepa_mandate', 'member', member.id);
        if (!existingKeys.has(key)) {
          pushSuggestion({
            taskType: 'member_missing_sepa_mandate',
            title: `SEPA-Mandat für ${name} einholen`,
            description: 'Für dieses aktive Mitglied liegt noch kein aktives SEPA-Mandat vor.',
            priority: 'high',
            dueAt: addDays(today, 5),
            automationSource: 'club_sepa_mandates',
            automationKey: key,
            suppressionToken: suggestionToken([member.updatedAt, 'sepa-mandate-missing']),
            sourceEntityType: 'member',
            sourceEntityId: member.id,
            sourceSnapshot: { memberName: name },
          });
        }
      }
    }

    for (const claim of paymentClaims) {
      const dueDate = claim.dueOn ? new Date(claim.dueOn) : today;
      const remainingAmountCents = Math.max(0, Number(claim.amountCents || 0) - Number(claim.paidAmountCents || 0));
      const claimTaskType = Number(claim.reminderLevel || 0) > 0
        ? 'payment_claim_reminder'
        : daysUntil(dueDate, today) < 0
          ? 'payment_claim_overdue'
          : 'payment_claim_due_soon';
      const key = buildAutomationKey(claimTaskType, 'club_payment_claim', claim.id);
      if (existingKeys.has(key)) continue;
      pushSuggestion({
        taskType: claimTaskType,
        title:
          claimTaskType === 'payment_claim_reminder'
            ? `${formatReminderStage(claim.reminderLevel)} für Forderung ${claim.id} prüfen`
            : claimTaskType === 'payment_claim_overdue'
              ? `Überfällige Zahlung ${claim.id} nachfassen`
              : `Fällige Zahlung ${claim.id} vorbereiten`,
        description:
          claimTaskType === 'payment_claim_reminder'
            ? `Offene Restforderung über ${(remainingAmountCents / 100).toFixed(2)} ${claim.currencyCode || 'EUR'} mit ${formatReminderStage(claim.reminderLevel)} prüfen.`
            : claimTaskType === 'payment_claim_overdue'
              ? `Überfällige Restforderung über ${(remainingAmountCents / 100).toFixed(2)} ${claim.currencyCode || 'EUR'} priorisiert nachverfolgen.`
              : `Restforderung über ${(remainingAmountCents / 100).toFixed(2)} ${claim.currencyCode || 'EUR'} vor Fälligkeit organisatorisch vorbereiten.`,
        priority: derivePriority(daysUntil(dueDate, today)),
        dueAt: dueDate,
        automationSource: 'club_payment_claims',
        automationKey: key,
        suppressionToken: suggestionToken([claim.updatedAt, claim.status, claim.reminderLevel, claim.dueOn]),
        sourceEntityType: 'club_payment_claim',
        sourceEntityId: claim.id,
        sourceSnapshot: {
          amountCents: Number(claim.amountCents),
          paidAmountCents: Number(claim.paidAmountCents || 0),
          remainingAmountCents,
          currencyCode: claim.currencyCode,
          dueOn: claim.dueOn,
          status: claim.status,
          reminderLevel: claim.reminderLevel,
          reminderStage: formatReminderStage(claim.reminderLevel),
        },
      });
    }

    for (const invoice of invoices) {
      const dueDate = invoice.dueOn ? new Date(invoice.dueOn) : today;
      const invoiceTaskType = invoice.invoiceDirection === 'incoming'
        ? (daysUntil(dueDate, today) < 0 ? 'invoice_incoming_overdue' : 'invoice_incoming_due_soon')
        : (daysUntil(dueDate, today) < 0 ? 'invoice_outgoing_overdue' : 'invoice_outgoing_due_soon');
      const key = buildAutomationKey(invoiceTaskType, 'club_invoice', invoice.id);
      if (existingKeys.has(key)) continue;
      pushSuggestion({
        taskType: invoiceTaskType,
        title: `${invoice.invoiceDirection === 'incoming' ? 'Eingangsrechnung' : 'Ausgangsrechnung'} ${invoice.invoiceNumber || `#${invoice.id}`} prüfen`,
        description: invoice.invoiceDirection === 'incoming'
          ? 'Offene Eingangsrechnung rechtzeitig für Zahlung oder Klärung vorbereiten.'
          : 'Offene Ausgangsrechnung auf Zahlungseingang und Erinnerung prüfen.',
        priority: derivePriority(daysUntil(dueDate, today)),
        dueAt: dueDate,
        automationSource: 'club_invoices',
        automationKey: key,
        suppressionToken: suggestionToken([invoice.updatedAt, invoice.status, invoice.dueOn, invoice.grossAmountCents]),
        sourceEntityType: 'club_invoice',
        sourceEntityId: invoice.id,
        sourceSnapshot: {
          invoiceNumber: invoice.invoiceNumber,
          invoiceDirection: invoice.invoiceDirection,
          amountCents: Number(invoice.grossAmountCents || 0),
          currencyCode: invoice.currencyCode || 'EUR',
          dueOn: invoice.dueOn,
        },
      });
    }

    for (const recipient of communicationRecipients) {
      const key = buildAutomationKey('communication_delivery_retry', 'club_communication_recipient', recipient.id);
      if (existingKeys.has(key)) continue;
      pushSuggestion({
        taskType: 'communication_delivery_retry',
        title: `Versandfehler für ${recipient.recipientName || 'Empfänger'} prüfen`,
        description: recipient.errorMessage || 'Retry-fähiger Versandfehler in der Vereinskommunikation.',
        priority: 'high',
        dueAt: addDays(today, 1),
        automationSource: 'club_communication',
        automationKey: key,
        suppressionToken: suggestionToken([recipient.updatedAt, recipient.errorCode, recipient.errorMessage]),
        sourceEntityType: 'club_communication_recipient',
        sourceEntityId: recipient.id,
        sourceSnapshot: {
          recipientName: recipient.recipientName,
          threadId: recipient.threadId,
          threadSubject: recipient.thread?.subject || null,
          errorMessage: recipient.errorMessage || null,
        },
      });
    }

    for (const event of events) {
      if (['cancelled', 'done'].includes(String(event.status || ''))) {
        continue;
      }
      if (hasExistingSourceTask(currentTasks, 'calendar_event', event.id, 'calendar_events')) continue;
      const referenceDate = event.registrationDeadline || event.startDate;
      const dueDate = referenceDate ? addDays(new Date(referenceDate), -3) : addDays(today, 7);
      const eventTaskType = daysUntil(dueDate, today) <= 1 ? 'calendar_event_deadline_check' : 'calendar_event_prepare';
      const key = buildAutomationKey(eventTaskType, 'calendar_event', event.id);
      if (existingKeys.has(key)) continue;
      pushSuggestion({
        taskType: eventTaskType,
        title:
          eventTaskType === 'calendar_event_deadline_check'
            ? `Letzte Prüfung für Termin: ${event.title}`
            : `Termin vorbereiten: ${event.title}`,
        description:
          eventTaskType === 'calendar_event_deadline_check'
            ? 'Kurz vor dem Termin noch einmal Kommunikation, Teilnehmerstand und letzte Freigaben prüfen.'
            : 'Termin organisatorisch prüfen, Verantwortliche abstimmen und offene Punkte schließen.',
        priority: derivePriority(daysUntil(dueDate, today)),
        dueAt: dueDate,
        automationSource: 'calendar_events',
        automationKey: key,
        suppressionToken: suggestionToken([event.updatedAt, event.startDate, event.endDate, event.registrationDeadline, event.status, event.eventType]),
        sourceEntityType: 'calendar_event',
        sourceEntityId: event.id,
        sourceSnapshot: {
          title: event.title,
          eventType: event.eventType || null,
          status: event.status || null,
          startDate: event.startDate,
          endDate: event.endDate,
          registrationDeadline: event.registrationDeadline || null,
          location: event.location || null,
          category: event.category || null,
        },
      });
    }

    suggestions.sort((left, right) => {
      const leftDue = left.dueAt ? new Date(left.dueAt).getTime() : Number.MAX_SAFE_INTEGER;
      const rightDue = right.dueAt ? new Date(right.dueAt).getTime() : Number.MAX_SAFE_INTEGER;
      return leftDue - rightDue;
    });

    return {
      definitions: CLUB_TASK_DEFINITIONS,
      workflowSources: CLUB_WORKFLOW_SOURCES,
      suggestions,
    };
  }

  async materializeSuggestions(clubId, userId, automationKeys = []) {
    const overview = await this.buildAutomationOverview(clubId);
    const matches = overview.suggestions.filter((suggestion) => automationKeys.includes(suggestion.automationKey));
    const tasks = [];

    for (const suggestion of matches) {
      const task = await ClubTask.create({
        clubId,
        title: suggestion.title,
        taskType: suggestion.taskType,
        description: suggestion.description,
        status: 'open',
        priority: suggestion.priority,
        dueAt: suggestion.dueAt,
        createdByUserId: userId || null,
        automationSource: suggestion.automationSource,
        automationKey: suggestion.automationKey,
        relatedEntityType: suggestion.sourceEntityType,
        relatedEntityId: suggestion.sourceEntityId,
        sourceSnapshot: suggestion.sourceSnapshot,
      });
      tasks.push(task);
    }

    return tasks;
  }

  buildFollowUpSuggestionFromTask(task, nextTaskType) {
    const definition = definitionMap[nextTaskType];
    if (!definition) {
      return null;
    }

    const snapshot = task.sourceSnapshot || {};
    const person = snapshot.person || snapshot.memberName || 'Unbekannt';
    const sourceDate = task.dueAt ? new Date(task.dueAt) : todayStart();

    switch (nextTaskType) {
      case 'request_trial_training_follow_up':
        return wrapSuggestion({
          taskType: nextTaskType,
          title: `Rückmeldung zu Probetraining von ${person} einholen`,
          description: 'Nach dem vereinbarten Probetraining Trainerfeedback und Rückmeldung des Interessenten einsammeln.',
          priority: 'high',
          dueAt: addDays(sourceDate, 2),
          automationSource: task.automationSource,
          automationKey: buildAutomationKey(nextTaskType, task.relatedEntityType, task.relatedEntityId),
          sourceEntityType: task.relatedEntityType,
          sourceEntityId: task.relatedEntityId,
          sourceSnapshot: snapshot,
        });
      case 'membership_prepare_admission':
        return wrapSuggestion({
          taskType: nextTaskType,
          title: `Aufnahme für ${person} vorbereiten`,
          description: 'Aufnahmeentscheidung vorbereiten, fehlende Freigaben klären und Übernahme in den Verein anstoßen.',
          priority: 'high',
          dueAt: addDays(sourceDate, 2),
          automationSource: task.automationSource,
          automationKey: buildAutomationKey(nextTaskType, task.relatedEntityType, task.relatedEntityId),
          sourceEntityType: task.relatedEntityType,
          sourceEntityId: task.relatedEntityId,
          sourceSnapshot: snapshot,
        });
      case 'membership_create_member_record':
        return wrapSuggestion({
          taskType: nextTaskType,
          title: `Mitgliedsdatensatz für ${person} anlegen`,
          description: 'Mitglied im System anlegen, Stammdaten prüfen und Vereinsstatus sauber setzen.',
          priority: 'high',
          dueAt: addDays(sourceDate, 1),
          automationSource: task.automationSource,
          automationKey: buildAutomationKey(nextTaskType, task.relatedEntityType, task.relatedEntityId),
          sourceEntityType: task.relatedEntityType,
          sourceEntityId: task.relatedEntityId,
          sourceSnapshot: snapshot,
        });
      case 'membership_collect_sepa_mandate':
        return wrapSuggestion({
          taskType: nextTaskType,
          title: `SEPA-Mandat für ${person} organisieren`,
          description: 'Für das neue Mitglied das SEPA-Mandat einholen oder auf Vollständigkeit prüfen.',
          priority: 'high',
          dueAt: addDays(sourceDate, 3),
          automationSource: task.automationSource,
          automationKey: buildAutomationKey(nextTaskType, task.relatedEntityType, task.relatedEntityId),
          sourceEntityType: task.relatedEntityType,
          sourceEntityId: task.relatedEntityId,
          sourceSnapshot: snapshot,
        });
      case 'membership_assign_fee':
        return wrapSuggestion({
          taskType: nextTaskType,
          title: `Beitragszuordnung für ${person} prüfen`,
          description: 'Beitragssatz, Ermäßigung oder Familienlogik für das neue Mitglied verbindlich festlegen.',
          priority: 'normal',
          dueAt: addDays(sourceDate, 2),
          automationSource: task.automationSource,
          automationKey: buildAutomationKey(nextTaskType, task.relatedEntityType, task.relatedEntityId),
          sourceEntityType: task.relatedEntityType,
          sourceEntityId: task.relatedEntityId,
          sourceSnapshot: snapshot,
        });
      case 'calendar_event_deadline_check':
        return wrapSuggestion({
          taskType: nextTaskType,
          title: `Letzte Prüfung für Termin: ${snapshot.title || task.title}`,
          description: 'Kurz vor dem Termin noch einmal Kommunikation, Teilnehmerstand und letzte Freigaben prüfen.',
          priority: 'high',
          dueAt: addDays(sourceDate, 1),
          automationSource: task.automationSource,
          automationKey: buildAutomationKey(nextTaskType, task.relatedEntityType, task.relatedEntityId),
          sourceEntityType: task.relatedEntityType,
          sourceEntityId: task.relatedEntityId,
          sourceSnapshot: snapshot,
        });
      case 'sponsoring_prepare_offer':
        return wrapSuggestion({
          taskType: nextTaskType,
          title: `Sponsoringangebot für ${person} vorbereiten`,
          description: 'Konkretes Sponsoringangebot auf Basis des Erstkontakts zusammenstellen.',
          priority: 'normal',
          dueAt: addDays(sourceDate, 2),
          automationSource: task.automationSource,
          automationKey: buildAutomationKey(nextTaskType, task.relatedEntityType, task.relatedEntityId),
          sourceEntityType: task.relatedEntityType,
          sourceEntityId: task.relatedEntityId,
          sourceSnapshot: snapshot,
        });
      case 'sponsoring_follow_up':
        return wrapSuggestion({
          taskType: nextTaskType,
          title: `Sponsoringnachfassen bei ${person}`,
          description: 'Nach Versand des Angebots Rückmeldung einholen und den nächsten Schritt abstimmen.',
          priority: 'normal',
          dueAt: addDays(sourceDate, 4),
          automationSource: task.automationSource,
          automationKey: buildAutomationKey(nextTaskType, task.relatedEntityType, task.relatedEntityId),
          sourceEntityType: task.relatedEntityType,
          sourceEntityId: task.relatedEntityId,
          sourceSnapshot: snapshot,
        });
      default:
        return null;
    }
  }

  async materializeWorkflowFollowUps(task, userId) {
    const definition = definitionMap[task.taskType];
    const nextTaskTypes = Array.isArray(definition?.nextTaskTypes) ? definition.nextTaskTypes : [];
    if (nextTaskTypes.length === 0) {
      return [];
    }

    const createdTasks = [];
    for (const nextTaskType of nextTaskTypes) {
      const followUp = this.buildFollowUpSuggestionFromTask(task, nextTaskType);
      if (!followUp) continue;

      const existingTask = await ClubTask.findOne({
        where: {
          clubId: task.clubId,
          automationKey: followUp.automationKey,
        },
      });
      if (existingTask) continue;

      const createdTask = await ClubTask.create({
        clubId: task.clubId,
        title: followUp.title,
        taskType: followUp.taskType,
        description: followUp.description,
        status: 'open',
        priority: followUp.priority,
        dueAt: followUp.dueAt,
        createdByUserId: userId || null,
        automationSource: followUp.automationSource,
        automationKey: followUp.automationKey,
        relatedEntityType: followUp.sourceEntityType,
        relatedEntityId: followUp.sourceEntityId,
        sourceSnapshot: followUp.sourceSnapshot,
      });
      createdTasks.push(createdTask);
    }

    return createdTasks;
  }

  async dismissSuggestion(clubId, userId, suggestionPayload = {}) {
    const automationKey = String(suggestionPayload.automationKey || '').trim();
    const suppressionTokenValue = String(suggestionPayload.suppressionToken || '').trim();

    if (!automationKey || !suppressionTokenValue) {
      const error = new Error('Automatik-Schlüssel und Unterdrückungs-Token sind erforderlich.');
      error.statusCode = 400;
      throw error;
    }

    const [row] = await ClubTaskSuppression.findOrCreate({
      where: { clubId, automationKey },
      defaults: {
        clubId,
        automationKey,
        suppressionToken: suppressionTokenValue,
        dismissedByUserId: userId || null,
      },
    });

    if (row.suppressionToken !== suppressionTokenValue || Number(row.dismissedByUserId || 0) !== Number(userId || 0)) {
      row.suppressionToken = suppressionTokenValue;
      row.dismissedByUserId = userId || null;
      await row.save();
    }

    return row;
  }
}

export default new ClubTaskAutomationService();
