import sequelize from '../database.js';
import {
  ClubCommunicationThread,
  ClubCommunicationMessage,
  ClubCommunicationRecipient,
  ClubCommunicationDeliveryLog,
  ClubCommunicationTemplate,
  ClubDistributionGroup,
  ClubDistributionGroupMember,
  ClubSepaMandate,
  Member,
  MemberContact,
  User,
} from '../models/index.js';
import { sendClubCommunicationEmail } from './emailService.js';

const THREAD_TYPES = new Set(['direct', 'group', 'broadcast']);
const THREAD_STATUSES = new Set(['draft', 'scheduled', 'sent', 'archived']);
const MESSAGE_TYPES = new Set(['message', 'note']);
const DIRECTIONS = new Set(['outbound', 'internal', 'inbound']);
const GROUP_TYPES = new Set(['manual', 'training_group', 'team', 'custom']);

function trimText(value, maxLength = null) {
  const normalized = String(value || '').trim();
  if (!normalized) return null;
  return maxLength ? normalized.slice(0, maxLength) : normalized;
}

function normalizeDate(value) {
  const normalized = String(value || '').trim();
  return normalized || null;
}

function normalizeId(value) {
  const numeric = Number(value);
  return Number.isInteger(numeric) && numeric > 0 ? numeric : null;
}

function normalizeThreadPayload(payload = {}) {
  return {
    threadType: THREAD_TYPES.has(payload.threadType) ? payload.threadType : 'direct',
    subject: trimText(payload.subject, 255),
    status: THREAD_STATUSES.has(payload.status) ? payload.status : 'draft',
    recipientMemberId: normalizeId(payload.recipientMemberId),
    distributionGroupId: normalizeId(payload.distributionGroupId),
    scheduledAt: normalizeDate(payload.scheduledAt),
    recipientFilters: normalizeRecipientFilters(payload.recipientFilters),
  };
}

function normalizeGroupPayload(payload = {}) {
  const memberIds = Array.isArray(payload.memberIds)
    ? [...new Set(payload.memberIds.map(normalizeId).filter(Boolean))]
    : [];

  return {
    name: trimText(payload.name, 255),
    description: trimText(payload.description),
    groupType: GROUP_TYPES.has(payload.groupType) ? payload.groupType : 'custom',
    memberIds,
    filterDefinition: normalizeRecipientFilters(payload.filterDefinition),
  };
}

function normalizeMessagePayload(payload = {}) {
  return {
    body: trimText(payload.body),
    messageType: MESSAGE_TYPES.has(payload.messageType) ? payload.messageType : 'message',
    direction: DIRECTIONS.has(payload.direction) ? payload.direction : 'outbound',
  };
}

function normalizeRecipientFilters(payload = {}) {
  return {
    activeOnly: payload?.activeOnly !== false,
    onlyWithEmail: Boolean(payload?.onlyWithEmail),
    missingEmail: Boolean(payload?.missingEmail),
    requiresSepaMandate: Boolean(payload?.requiresSepaMandate),
    missingSepaMandate: Boolean(payload?.missingSepaMandate),
    testMembersOnly: Boolean(payload?.testMembersOnly),
  };
}

function normalizeTemplatePayload(payload = {}) {
  return {
    name: trimText(payload.name, 160),
    category: trimText(payload.category, 64) || 'general',
    subjectTemplate: trimText(payload.subjectTemplate, 255),
    bodyTemplate: trimText(payload.bodyTemplate),
    variablesHint: trimText(payload.variablesHint),
  };
}

function ensureThreadTargets(payload) {
  if (!payload.subject) {
    const error = new Error('Betreff ist erforderlich.');
    error.status = 400;
    throw error;
  }
  if (payload.threadType === 'direct' && !payload.recipientMemberId) {
    const error = new Error('Für Einzelnachrichten muss ein Mitglied ausgewählt werden.');
    error.status = 400;
    throw error;
  }
  if (payload.threadType === 'group' && !payload.distributionGroupId) {
    const error = new Error('Für Gruppen-Nachrichten muss eine Verteilergruppe ausgewählt werden.');
    error.status = 400;
    throw error;
  }
}

async function ensureMemberBelongsToClub(clubId, memberId) {
  if (!memberId) return;
  const member = await Member.findOne({ where: { id: memberId, clubId } });
  if (!member) {
    const error = new Error('Mitglied wurde nicht gefunden.');
    error.status = 404;
    throw error;
  }
}

async function ensureGroupBelongsToClub(clubId, groupId) {
  if (!groupId) return null;
  const group = await ClubDistributionGroup.findOne({ where: { id: groupId, clubId } });
  if (!group) {
    const error = new Error('Verteilergruppe wurde nicht gefunden.');
    error.status = 404;
    throw error;
  }
  return group;
}

function getMemberDisplayName(member) {
  return [member?.firstName, member?.lastName].filter(Boolean).join(' ').trim() || `Mitglied ${member?.id || ''}`.trim();
}

function getPrimaryEmail(member) {
  const contacts = Array.isArray(member?.contacts) ? member.contacts : [];
  const emailContacts = contacts.filter((contact) => contact?.type === 'email' && contact?.value);
  const primaryContact = emailContacts.find((contact) => contact.isPrimary) || emailContacts[0];
  return primaryContact?.value || member?.email || null;
}

function applyRecipientFilters(members, filters = {}, memberIdsWithMandate = new Set()) {
  const normalized = normalizeRecipientFilters(filters);
  return (Array.isArray(members) ? members : []).filter((member) => {
    if (normalized.activeOnly && member?.active === false) return false;
    if (normalized.testMembersOnly && !member?.testMembership) return false;

    const email = trimText(getPrimaryEmail(member), 255);
    if (normalized.onlyWithEmail && !email) return false;
    if (normalized.missingEmail && email) return false;

    const hasMandate = memberIdsWithMandate.has(Number(member?.id));
    if (normalized.requiresSepaMandate && !hasMandate) return false;
    if (normalized.missingSepaMandate && hasMandate) return false;

    return true;
  });
}

async function loadActiveSepaMemberIds(clubId) {
  const mandates = await ClubSepaMandate.findAll({
    where: {
      clubId,
      status: 'active',
      revokedAt: null,
    },
    attributes: ['memberId'],
  });

  return new Set(mandates.map((mandate) => Number(mandate.memberId)).filter(Boolean));
}

function escapeHtml(value) {
  return String(value ?? '')
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;');
}

function looksLikeHtml(value = '') {
  return /<\/?[a-z][\s\S]*>/i.test(String(value || ''));
}

function stripHtml(value = '') {
  return String(value || '')
    .replace(/<br\s*\/?>/gi, '\n')
    .replace(/<\/p>\s*<p>/gi, '\n\n')
    .replace(/<\/div>\s*<div>/gi, '\n')
    .replace(/<li[^>]*>/gi, '\n- ')
    .replace(/<\/(p|div|li|h[1-6]|blockquote)>/gi, '\n')
    .replace(/<[^>]+>/g, '')
    .replace(/\n{3,}/g, '\n\n')
    .trim();
}

function renderMessageText(body) {
  if (looksLikeHtml(body)) {
    return stripHtml(body);
  }
  return String(body || '').trim();
}

function renderMessageHtml(body) {
  if (looksLikeHtml(body)) {
    return `<div style="font-family:Arial,sans-serif;font-size:15px;line-height:1.55;color:#1f2937;">${String(body || '')}</div>`;
  }
  return `<div style="font-family:Arial,sans-serif;font-size:15px;line-height:1.55;color:#1f2937;white-space:pre-wrap;">${escapeHtml(body).replace(/\n/g, '<br>')}</div>`;
}

function getLatestOutboundMessage(messages = []) {
  const outboundMessages = messages.filter((message) => message?.messageType === 'message' && message?.direction === 'outbound');
  return outboundMessages.at(-1) || null;
}

function classifyDeliveryError(error) {
  const responseCode = Number(error?.responseCode || error?.response?.statusCode || 0) || null;
  const code = String(error?.code || '').trim() || `SMTP_${responseCode || 'UNKNOWN'}`;
  const message = trimText(error?.message, 500) || 'Unbekannter Versandfehler.';
  const retryableCodes = new Set([
    'ETIMEDOUT',
    'ESOCKET',
    'ECONNECTION',
    'ECONNRESET',
    'EAI_AGAIN',
    'ENOTFOUND',
    'EMESSAGE',
    'EMAIL_CONFIG_MISSING',
  ]);
  const retryableResponseCodes = new Set([421, 425, 429, 450, 451, 452]);
  const retryable = retryableCodes.has(code) || (responseCode ? retryableResponseCodes.has(responseCode) : false);

  return {
    code,
    message,
    retryable,
    responseCode,
  };
}

function canAttemptDelivery(recipient) {
  const hasEmail = !!trimText(recipient?.emailSnapshot, 255);
  if (!hasEmail) {
    return {
      allowed: false,
      reason: 'Keine E-Mail-Adresse hinterlegt.',
      retryable: false,
      code: 'MISSING_EMAIL',
    };
  }

  if (recipient?.deliveryStatus === 'sent') {
    return {
      allowed: false,
      reason: 'Empfänger wurde bereits erfolgreich beliefert.',
      retryable: false,
      code: 'ALREADY_SENT',
    };
  }

  if (recipient?.deliveryStatus === 'failed' && recipient?.retryable === false) {
    return {
      allowed: false,
      reason: recipient?.errorMessage || 'Versandfehler ist nicht erneut versendbar.',
      retryable: false,
      code: recipient?.errorCode || 'NON_RETRYABLE_FAILURE',
    };
  }

  return {
    allowed: true,
    reason: '',
    retryable: true,
    code: null,
  };
}

async function buildRecipientRowsForThread(clubId, threadPayload) {
  let members = [];
  const memberIdsWithMandate = await loadActiveSepaMemberIds(clubId);

  if (threadPayload.threadType === 'direct' && threadPayload.recipientMemberId) {
    members = await Member.findAll({
      where: { clubId, id: threadPayload.recipientMemberId },
      include: [{ model: MemberContact, as: 'contacts', required: false }],
    });
  } else if (threadPayload.threadType === 'group' && threadPayload.distributionGroupId) {
    const group = await ClubDistributionGroup.findOne({
      where: { id: threadPayload.distributionGroupId, clubId },
    });
    const memberships = await ClubDistributionGroupMember.findAll({
      where: { groupId: threadPayload.distributionGroupId },
      include: [{
        model: Member,
        as: 'member',
        required: true,
        where: { clubId, active: true },
        include: [{ model: MemberContact, as: 'contacts', required: false }],
      }],
    });
    members = applyRecipientFilters(
      memberships.map((membership) => membership.member).filter(Boolean),
      threadPayload.recipientFilters || group?.filterDefinition || {},
      memberIdsWithMandate
    );
  } else if (threadPayload.threadType === 'broadcast') {
    members = await Member.findAll({
      where: { clubId, active: true },
      include: [{ model: MemberContact, as: 'contacts', required: false }],
      order: [['lastName', 'ASC'], ['firstName', 'ASC']],
    });
    members = applyRecipientFilters(members, threadPayload.recipientFilters || {}, memberIdsWithMandate);
  }

  const uniqueMembers = new Map();
  members.forEach((member) => {
    const memberId = Number(member.id);
    if (!uniqueMembers.has(memberId)) {
      uniqueMembers.set(memberId, member);
    }
  });

    return Array.from(uniqueMembers.values()).map((member) => {
      const emailSnapshot = getPrimaryEmail(member);
      return {
        clubId,
        memberId: Number(member.id),
        recipientName: getMemberDisplayName(member),
        emailSnapshot: emailSnapshot || null,
        deliveryStatus: emailSnapshot ? 'pending' : 'failed',
        deliveredAt: null,
        lastAttemptAt: null,
        attemptCount: 0,
        retryable: false,
        errorCode: emailSnapshot ? null : 'MISSING_EMAIL',
        transportMessageId: null,
        errorMessage: emailSnapshot ? null : 'Keine E-Mail-Adresse hinterlegt.',
      };
    });
}

async function replaceRecipientsForThread(thread, threadPayload, transaction) {
  const recipients = await buildRecipientRowsForThread(thread.clubId, threadPayload);
  await ClubCommunicationDeliveryLog.destroy({
    where: { threadId: thread.id, clubId: thread.clubId },
    transaction,
  });
  await ClubCommunicationRecipient.destroy({
    where: { threadId: thread.id, clubId: thread.clubId },
    transaction,
  });
  if (recipients.length > 0) {
    await ClubCommunicationRecipient.bulkCreate(
      recipients.map((recipient) => ({
        ...recipient,
        threadId: thread.id,
      })),
      { transaction }
    );
  }
}

class ClubCommunicationService {
  async listClubCommunication(clubId) {
    const [threads, groups, members, templates] = await Promise.all([
      ClubCommunicationThread.findAll({
        where: { clubId },
        include: [
          { model: ClubCommunicationMessage, as: 'messages', required: false, include: [{ model: User, as: 'createdByUser', required: false }] },
          { model: ClubCommunicationRecipient, as: 'recipients', required: false, include: [{ model: Member, as: 'member', required: false }] },
          {
            model: ClubCommunicationDeliveryLog,
            as: 'deliveryLogs',
            required: false,
            include: [
              { model: ClubCommunicationRecipient, as: 'recipient', required: false },
              { model: User, as: 'createdByUser', required: false },
            ],
          },
          { model: ClubDistributionGroup, as: 'distributionGroup', required: false },
          { model: Member, as: 'recipientMember', required: false },
          { model: User, as: 'createdByUser', required: false },
        ],
        order: [
          ['updatedAt', 'DESC'],
          [{ model: ClubCommunicationMessage, as: 'messages' }, 'createdAt', 'ASC'],
          [{ model: ClubCommunicationRecipient, as: 'recipients' }, 'recipientName', 'ASC'],
          [{ model: ClubCommunicationDeliveryLog, as: 'deliveryLogs' }, 'createdAt', 'DESC'],
        ],
      }),
      ClubDistributionGroup.findAll({
        where: { clubId },
        include: [
          {
            model: ClubDistributionGroupMember,
            as: 'memberships',
            required: false,
            include: [{ model: Member, as: 'member', required: false }],
          },
        ],
        order: [['isSystemGroup', 'DESC'], ['name', 'ASC']],
      }),
      Member.findAll({
        where: { clubId, active: true },
        order: [['lastName', 'ASC'], ['firstName', 'ASC']],
      }),
      ClubCommunicationTemplate.findAll({
        where: { clubId },
        order: [['sortOrder', 'ASC'], ['name', 'ASC']],
      }),
    ]);

    return { threads, groups, members, templates };
  }

  async createThread(clubId, userId, payload) {
    const normalized = normalizeThreadPayload(payload);
    ensureThreadTargets(normalized);
    await ensureMemberBelongsToClub(clubId, normalized.recipientMemberId);
    await ensureGroupBelongsToClub(clubId, normalized.distributionGroupId);

    return sequelize.transaction(async (transaction) => {
      const thread = await ClubCommunicationThread.create({
        clubId,
        createdByUserId: userId || null,
        ...normalized,
      }, { transaction });
      await replaceRecipientsForThread(thread, normalized, transaction);
      return thread;
    });
  }

  async updateThread(clubId, threadId, payload) {
    const thread = await ClubCommunicationThread.findOne({ where: { id: threadId, clubId } });
    if (!thread) {
      const error = new Error('Kommunikationsvorgang wurde nicht gefunden.');
      error.status = 404;
      throw error;
    }

    const normalized = normalizeThreadPayload(payload);
    ensureThreadTargets(normalized);
    await ensureMemberBelongsToClub(clubId, normalized.recipientMemberId);
    await ensureGroupBelongsToClub(clubId, normalized.distributionGroupId);

    await sequelize.transaction(async (transaction) => {
      await thread.update({
        ...normalized,
        sentAt: normalized.status === 'sent' ? (thread.sentAt || new Date()) : (normalized.status === 'archived' ? thread.sentAt : null),
      }, { transaction });
      await replaceRecipientsForThread(thread, normalized, transaction);
    });
    return thread;
  }

  async addMessage(clubId, threadId, userId, payload) {
    const thread = await ClubCommunicationThread.findOne({ where: { id: threadId, clubId } });
    if (!thread) {
      const error = new Error('Kommunikationsvorgang wurde nicht gefunden.');
      error.status = 404;
      throw error;
    }

    const normalized = normalizeMessagePayload(payload);
    if (!normalized.body) {
      const error = new Error('Nachrichtentext ist erforderlich.');
      error.status = 400;
      throw error;
    }

    const message = await ClubCommunicationMessage.create({
      threadId: thread.id,
      clubId,
      createdByUserId: userId || null,
      ...normalized,
    });

    await thread.update({
      status: thread.status === 'archived' ? 'archived' : thread.status,
      sentAt: normalized.direction === 'outbound' && normalized.messageType === 'message' ? (thread.sentAt || new Date()) : thread.sentAt,
    });

    return message;
  }

  async sendThread(clubId, threadId, userId = null) {
    const thread = await ClubCommunicationThread.findOne({
      where: { id: threadId, clubId },
      include: [
        {
          model: ClubCommunicationMessage,
          as: 'messages',
          required: false,
          order: [['createdAt', 'ASC']],
        },
        {
          model: ClubCommunicationRecipient,
          as: 'recipients',
          required: false,
          order: [['recipientName', 'ASC']],
        },
      ],
    });
    if (!thread) {
      const error = new Error('Kommunikationsvorgang wurde nicht gefunden.');
      error.status = 404;
      throw error;
    }

    const latestMessage = getLatestOutboundMessage(thread.messages || []);
    if (!latestMessage?.body) {
      const error = new Error('Zum Versand wird mindestens eine ausgehende Nachricht benötigt.');
      error.status = 400;
      throw error;
    }

    const recipients = Array.isArray(thread.recipients) ? thread.recipients : [];
    let attemptedAny = false;
    let processedAny = false;

    if (recipients.length === 0) {
      const error = new Error('Für diesen Kommunikationsvorgang sind keine Empfänger vorhanden.');
      error.status = 400;
      throw error;
    }

    for (const recipient of recipients) {
      const eligibility = canAttemptDelivery(recipient);
      if (!eligibility.allowed) {
        if (!recipient.attemptCount && eligibility.code === 'MISSING_EMAIL') {
          const now = new Date();
          processedAny = true;
          await sequelize.transaction(async (transaction) => {
            await recipient.update({
              deliveryStatus: 'failed',
              lastAttemptAt: now,
              attemptCount: Number(recipient.attemptCount || 0) + 1,
              retryable: false,
              errorCode: eligibility.code,
              errorMessage: eligibility.reason,
              deliveredAt: null,
              transportMessageId: null,
            }, { transaction });

            await ClubCommunicationDeliveryLog.create({
              clubId,
              threadId: thread.id,
              recipientId: recipient.id,
              createdByUserId: userId || null,
              status: 'failed',
              attemptNo: Number(recipient.attemptCount || 0) + 1,
              retryable: false,
              errorCode: eligibility.code,
              errorMessage: eligibility.reason,
            }, { transaction });
          });
        }
        continue;
      }

      attemptedAny = true;
      processedAny = true;
      const nextAttemptNo = Number(recipient.attemptCount || 0) + 1;
      const now = new Date();

      try {
        const result = await sendClubCommunicationEmail({
          to: recipient.emailSnapshot,
          subject: thread.subject,
          text: renderMessageText(latestMessage.body),
          html: renderMessageHtml(latestMessage.body),
        });

        await sequelize.transaction(async (transaction) => {
          await recipient.update({
            deliveryStatus: 'sent',
            deliveredAt: now,
            lastAttemptAt: now,
            attemptCount: nextAttemptNo,
            retryable: false,
            errorCode: null,
            errorMessage: null,
            transportMessageId: trimText(result?.messageId, 255),
          }, { transaction });

          await ClubCommunicationDeliveryLog.create({
            clubId,
            threadId: thread.id,
            recipientId: recipient.id,
            createdByUserId: userId || null,
            status: 'sent',
            attemptNo: nextAttemptNo,
            retryable: false,
            transportMessageId: trimText(result?.messageId, 255),
            transportResponse: trimText(result?.response, 1000),
          }, { transaction });
        });
      } catch (sendError) {
        const classified = classifyDeliveryError(sendError);
        console.error('[sendClubCommunicationThread] delivery failed', {
          clubId,
          threadId: thread.id,
          recipientId: recipient.id,
          email: recipient.emailSnapshot,
          code: classified.code,
          retryable: classified.retryable,
          message: classified.message,
        });

        await sequelize.transaction(async (transaction) => {
          await recipient.update({
            deliveryStatus: 'failed',
            deliveredAt: null,
            lastAttemptAt: now,
            attemptCount: nextAttemptNo,
            retryable: classified.retryable,
            errorCode: classified.code,
            errorMessage: classified.message,
            transportMessageId: null,
          }, { transaction });

          await ClubCommunicationDeliveryLog.create({
            clubId,
            threadId: thread.id,
            recipientId: recipient.id,
            createdByUserId: userId || null,
            status: 'failed',
            attemptNo: nextAttemptNo,
            retryable: classified.retryable,
            errorCode: classified.code,
            errorMessage: classified.message,
            transportResponse: trimText(sendError?.response, 1000),
          }, { transaction });
        });
      }
    }

    if (!processedAny) {
      const error = new Error('Es gibt aktuell keine erneut versendbaren Empfänger.');
      error.status = 400;
      throw error;
    }

    await thread.update({
      status: 'sent',
      sentAt: thread.sentAt || new Date(),
    });
    return thread;
  }

  async deleteThread(clubId, threadId) {
    const thread = await ClubCommunicationThread.findOne({ where: { id: threadId, clubId } });
    if (!thread) {
      const error = new Error('Kommunikationsvorgang wurde nicht gefunden.');
      error.status = 404;
      throw error;
    }

    await sequelize.transaction(async (transaction) => {
      await ClubCommunicationMessage.destroy({
        where: { threadId: thread.id, clubId },
        transaction,
      });
      await ClubCommunicationRecipient.destroy({
        where: { threadId: thread.id, clubId },
        transaction,
      });
      await ClubCommunicationDeliveryLog.destroy({
        where: { threadId: thread.id, clubId },
        transaction,
      });
      await thread.destroy({ transaction });
    });
  }

  async createGroup(clubId, payload) {
    const normalized = normalizeGroupPayload(payload);
    if (!normalized.name) {
      const error = new Error('Gruppenname ist erforderlich.');
      error.status = 400;
      throw error;
    }

    return sequelize.transaction(async (transaction) => {
      const group = await ClubDistributionGroup.create({
        clubId,
        name: normalized.name,
        description: normalized.description,
        groupType: normalized.groupType,
        filterDefinition: normalized.filterDefinition,
      }, { transaction });

      if (normalized.memberIds.length > 0) {
        await ClubDistributionGroupMember.bulkCreate(
          normalized.memberIds.map((memberId) => ({ groupId: group.id, memberId })),
          { transaction }
        );
      }

      return group;
    });
  }

  async updateGroup(clubId, groupId, payload) {
    const group = await ClubDistributionGroup.findOne({ where: { id: groupId, clubId } });
    if (!group) {
      const error = new Error('Verteilergruppe wurde nicht gefunden.');
      error.status = 404;
      throw error;
    }

    const normalized = normalizeGroupPayload(payload);
    if (!normalized.name) {
      const error = new Error('Gruppenname ist erforderlich.');
      error.status = 400;
      throw error;
    }

    await sequelize.transaction(async (transaction) => {
      await group.update({
        name: normalized.name,
        description: normalized.description,
        groupType: normalized.groupType,
        filterDefinition: normalized.filterDefinition,
      }, { transaction });

      await ClubDistributionGroupMember.destroy({
        where: { groupId: group.id },
        transaction,
      });

      if (normalized.memberIds.length > 0) {
        await ClubDistributionGroupMember.bulkCreate(
          normalized.memberIds.map((memberId) => ({ groupId: group.id, memberId })),
          { transaction }
        );
      }
    });

    return group;
  }

  async deleteGroup(clubId, groupId) {
    const group = await ClubDistributionGroup.findOne({ where: { id: groupId, clubId } });
    if (!group) {
      const error = new Error('Verteilergruppe wurde nicht gefunden.');
      error.status = 404;
      throw error;
    }

    const linkedThreads = await ClubCommunicationThread.count({
      where: { clubId, distributionGroupId: group.id },
    });
    if (linkedThreads > 0) {
      const error = new Error('Verteilergruppe kann nicht gelöscht werden, weil noch Kommunikationsvorgänge darauf verweisen.');
      error.status = 400;
      throw error;
    }

    await sequelize.transaction(async (transaction) => {
      await ClubDistributionGroupMember.destroy({ where: { groupId: group.id }, transaction });
      await group.destroy({ transaction });
    });
  }

  async createTemplate(clubId, payload) {
    const normalized = normalizeTemplatePayload(payload);
    if (!normalized.name || !normalized.bodyTemplate) {
      const error = new Error('Vorlagenname und Nachrichtentext sind erforderlich.');
      error.status = 400;
      throw error;
    }

    return ClubCommunicationTemplate.create({
      clubId,
      ...normalized,
    });
  }

  async updateTemplate(clubId, templateId, payload) {
    const template = await ClubCommunicationTemplate.findOne({
      where: { id: templateId, clubId },
    });
    if (!template) {
      const error = new Error('Vorlage wurde nicht gefunden.');
      error.status = 404;
      throw error;
    }
    if (template.isSystemTemplate) {
      const error = new Error('Systemvorlagen können nicht bearbeitet werden.');
      error.status = 400;
      throw error;
    }

    const normalized = normalizeTemplatePayload(payload);
    if (!normalized.name || !normalized.bodyTemplate) {
      const error = new Error('Vorlagenname und Nachrichtentext sind erforderlich.');
      error.status = 400;
      throw error;
    }

    await template.update(normalized);
    return template;
  }

  async deleteTemplate(clubId, templateId) {
    const template = await ClubCommunicationTemplate.findOne({
      where: { id: templateId, clubId },
    });
    if (!template) {
      const error = new Error('Vorlage wurde nicht gefunden.');
      error.status = 404;
      throw error;
    }
    if (template.isSystemTemplate) {
      const error = new Error('Systemvorlagen können nicht gelöscht werden.');
      error.status = 400;
      throw error;
    }

    await template.destroy();
    return { success: true };
  }
}

export default new ClubCommunicationService();
