import { ClubPaymentClaim, ClubRequest, Member } from '../models/index.js';

function isMissingTableError(error, tableName) {
  return error?.original?.code === 'ER_NO_SUCH_TABLE'
    && String(error?.original?.sqlMessage || '').includes(tableName);
}

function getMonthKey(dateLike) {
  const date = new Date(dateLike);
  if (Number.isNaN(date.getTime())) return null;
  return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`;
}

function buildLastMonthsTemplate(monthCount = 12) {
  const months = [];
  const cursor = new Date();
  cursor.setDate(1);

  for (let i = monthCount - 1; i >= 0; i -= 1) {
    const monthDate = new Date(cursor.getFullYear(), cursor.getMonth() - i, 1);
    months.push({
      key: getMonthKey(monthDate),
      label: `${String(monthDate.getMonth() + 1).padStart(2, '0')}.${monthDate.getFullYear()}`,
      newMembers: 0,
      memberCountSnapshot: 0,
      claimOpenAmountCents: 0,
      claimPaidAmountCents: 0,
      claimOpenCount: 0,
      claimPaidCount: 0,
      sponsorRequests: 0,
      sponsorOpenRequests: 0,
      sponsorConvertedRequests: 0,
    });
  }

  return months;
}

function getAgeFromBirthDate(birthDate) {
  if (!birthDate || typeof birthDate !== 'string') return null;
  const value = birthDate.trim();
  if (!value) return null;

  let year;
  let month;
  let day;

  const iso = value.match(/^(\d{4})-(\d{2})-(\d{2})$/);
  const german = value.match(/^(\d{1,2})\.(\d{1,2})\.(\d{4})$/);

  if (iso) {
    year = Number(iso[1]);
    month = Number(iso[2]) - 1;
    day = Number(iso[3]);
  } else if (german) {
    year = Number(german[3]);
    month = Number(german[2]) - 1;
    day = Number(german[1]);
  } else {
    const parsed = new Date(value);
    if (Number.isNaN(parsed.getTime())) return null;
    year = parsed.getFullYear();
    month = parsed.getMonth();
    day = parsed.getDate();
  }

  const birth = new Date(year, month, day);
  if (Number.isNaN(birth.getTime())) return null;

  const today = new Date();
  let age = today.getFullYear() - birth.getFullYear();
  const monthDiff = today.getMonth() - birth.getMonth();
  if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birth.getDate())) {
    age -= 1;
  }
  return age >= 0 ? age : null;
}

function getAgeBucket(age) {
  if (age == null) return null;
  if (age < 12) return 'under_12';
  if (age < 18) return '12_17';
  if (age < 27) return '18_26';
  if (age < 41) return '27_40';
  if (age < 61) return '41_60';
  return '61_plus';
}

const AGE_BUCKET_LABELS = {
  under_12: 'Unter 12',
  '12_17': '12 bis 17',
  '18_26': '18 bis 26',
  '27_40': '27 bis 40',
  '41_60': '41 bis 60',
  '61_plus': '61+',
};

class ClubStatisticsService {
  async getClubStatistics(clubIdRaw) {
    const clubId = Number.parseInt(clubIdRaw, 10);
    if (!Number.isFinite(clubId)) {
      const error = new Error('Ungültige clubId');
      error.status = 400;
      throw error;
    }

    const months = buildLastMonthsTemplate(12);
    const monthMap = new Map(months.map((entry) => [entry.key, entry]));
    const currentYear = new Date().getFullYear();

    const members = await Member.findAll({
      where: { clubId },
      order: [['createdAt', 'ASC']],
    });

    let paymentClaims = [];
    try {
      paymentClaims = await ClubPaymentClaim.findAll({
        where: { clubId },
        order: [['dueOn', 'ASC']],
      });
    } catch (error) {
      if (!isMissingTableError(error, 'club_payment_claims')) {
        throw error;
      }
    }

    let sponsorRequests = [];
    try {
      sponsorRequests = await ClubRequest.findAll({
        where: {
          clubId,
          requestType: 'sponsoring',
        },
        order: [['receivedAt', 'ASC']],
      });
    } catch (error) {
      if (!isMissingTableError(error, 'club_requests')) {
        throw error;
      }
    }

    const memberOverview = {
      activeMembers: members.filter((member) => member.active && !member.testMembership).length,
      inactiveMembers: members.filter((member) => !member.active).length,
      testMembers: members.filter((member) => member.testMembership).length,
      createdThisYear: members.filter((member) => new Date(member.createdAt).getFullYear() === currentYear).length,
    };

    const cumulativeMembers = [];
    let runningTotal = 0;
    for (const month of months) {
      const monthDate = new Date(`${month.key}-01T00:00:00`);
      const monthEnd = new Date(monthDate.getFullYear(), monthDate.getMonth() + 1, 0, 23, 59, 59, 999);
      const newMembers = members.filter((member) => {
        const createdAt = new Date(member.createdAt);
        return createdAt >= monthDate && createdAt <= monthEnd;
      }).length;
      month.newMembers = newMembers;
      runningTotal += newMembers;
      month.memberCountSnapshot = runningTotal;
      cumulativeMembers.push(month);
    }

    const ageCounts = {
      under_12: 0,
      '12_17': 0,
      '18_26': 0,
      '27_40': 0,
      '41_60': 0,
      '61_plus': 0,
    };
    let missingBirthdates = 0;
    let knownBirthdates = 0;
    for (const member of members.filter((entry) => entry.active)) {
      const age = getAgeFromBirthDate(member.birthDate);
      const bucket = getAgeBucket(age);
      if (!bucket) {
        missingBirthdates += 1;
        continue;
      }
      knownBirthdates += 1;
      ageCounts[bucket] += 1;
    }

    const paymentTotals = {
      openCount: 0,
      openAmountCents: 0,
      paidCount: 0,
      paidAmountCents: 0,
      overdueCount: 0,
      overdueAmountCents: 0,
    };
    const today = new Date();
    for (const claim of paymentClaims) {
      const amount = Number(claim.amountCents || 0);
      const paidAmount = Math.max(0, Number(claim.paidAmountCents || 0));
      const remainingAmount = Math.max(0, amount - paidAmount);
      const dueDate = claim.dueOn ? new Date(claim.dueOn) : null;
      const monthKey = getMonthKey(claim.dueOn || claim.createdAt);
      const monthEntry = monthKey ? monthMap.get(monthKey) : null;

      if (claim.status === 'paid') {
        paymentTotals.paidCount += 1;
        paymentTotals.paidAmountCents += paidAmount || amount;
        if (monthEntry) {
          monthEntry.claimPaidCount += 1;
          monthEntry.claimPaidAmountCents += paidAmount || amount;
        }
      } else if (['open', 'partially_paid'].includes(claim.status)) {
        paymentTotals.openCount += 1;
        paymentTotals.openAmountCents += remainingAmount;
        if (dueDate && dueDate < today) {
          paymentTotals.overdueCount += 1;
          paymentTotals.overdueAmountCents += remainingAmount;
        }
        if (monthEntry) {
          monthEntry.claimOpenCount += 1;
          monthEntry.claimOpenAmountCents += remainingAmount;
        }
      }
    }

    const sponsorTotals = {
      totalRequests: sponsorRequests.length,
      openRequests: sponsorRequests.filter((entry) => ['open', 'in_progress', 'waiting'].includes(entry.status)).length,
      convertedRequests: sponsorRequests.filter((entry) => entry.status === 'converted').length,
      archivedRequests: sponsorRequests.filter((entry) => ['archived', 'rejected'].includes(entry.status)).length,
    };
    for (const request of sponsorRequests) {
      const monthKey = getMonthKey(request.receivedAt || request.createdAt);
      const monthEntry = monthKey ? monthMap.get(monthKey) : null;
      if (!monthEntry) continue;
      monthEntry.sponsorRequests += 1;
      if (['open', 'in_progress', 'waiting'].includes(request.status)) {
        monthEntry.sponsorOpenRequests += 1;
      }
      if (request.status === 'converted') {
        monthEntry.sponsorConvertedRequests += 1;
      }
    }

    return {
      overview: memberOverview,
      memberDevelopment: {
        monthly: cumulativeMembers.map((entry) => ({
          key: entry.key,
          label: entry.label,
          newMembers: entry.newMembers,
          memberCountSnapshot: entry.memberCountSnapshot,
        })),
      },
      ageStructure: {
        knownBirthdates,
        missingBirthdates,
        buckets: Object.entries(ageCounts).map(([key, count]) => ({
          key,
          label: AGE_BUCKET_LABELS[key] || key,
          count,
        })),
      },
      contributionDevelopment: {
        totals: paymentTotals,
        monthly: months.map((entry) => ({
          key: entry.key,
          label: entry.label,
          openAmountCents: entry.claimOpenAmountCents,
          paidAmountCents: entry.claimPaidAmountCents,
          openCount: entry.claimOpenCount,
          paidCount: entry.claimPaidCount,
        })),
      },
      sponsorDevelopment: {
        totals: sponsorTotals,
        monthly: months.map((entry) => ({
          key: entry.key,
          label: entry.label,
          totalRequests: entry.sponsorRequests,
          openRequests: entry.sponsorOpenRequests,
          convertedRequests: entry.sponsorConvertedRequests,
        })),
      },
    };
  }
}

export default new ClubStatisticsService();
