import FriendlyMatch from '../models/FriendlyMatch.js';
import Member from '../models/Member.js';
import { checkAccess } from '../utils/userUtils.js';
import HttpError from '../exceptions/HttpError.js';

function cleanString(value, fallback = '') {
  const text = String(value ?? '').trim();
  return text || fallback;
}

function cleanOptionalString(value) {
  const text = String(value ?? '').trim();
  return text || null;
}

function normalizeParticipantList(list) {
  if (typeof list === 'string') {
    try {
      list = JSON.parse(list);
    } catch (error) {
      return [];
    }
  }
  if (!Array.isArray(list)) return [];
  return list
    .map((entry) => {
      const type = entry?.type === 'member' ? 'member' : 'manual';
      if (type === 'member') {
        const memberId = Number.parseInt(entry?.memberId, 10);
        if (!Number.isInteger(memberId)) return null;
        return { type, memberId };
      }
      const firstName = cleanString(entry?.firstName);
      const lastName = cleanString(entry?.lastName);
      if (!firstName && !lastName) return null;
      return { type, firstName, lastName };
    })
    .filter(Boolean);
}

function normalizeArrayValue(value) {
  if (Array.isArray(value)) return value;
  if (typeof value === 'string') {
    try {
      const parsed = JSON.parse(value);
      return Array.isArray(parsed) ? parsed : [];
    } catch (error) {
      return [];
    }
  }
  return [];
}

function normalizeIdList(list) {
  if (typeof list === 'string') {
    try {
      list = JSON.parse(list);
    } catch (error) {
      return null;
    }
  }
  if (!Array.isArray(list)) return null;
  const seen = new Set();
  const result = [];
  for (const value of list) {
    const id = Number.parseInt(value, 10);
    if (!Number.isInteger(id) || seen.has(id)) continue;
    seen.add(id);
    result.push(id);
  }
  return result;
}

function memberIdsFromParticipants(...participantLists) {
  const seen = new Set();
  const result = [];
  for (const list of participantLists) {
    for (const participant of normalizeParticipantList(list)) {
      if (participant.type !== 'member') continue;
      const id = Number.parseInt(participant.memberId, 10);
      if (!Number.isInteger(id) || seen.has(id)) continue;
      seen.add(id);
      result.push(id);
    }
  }
  return result;
}

function mergeIdLists(...lists) {
  const seen = new Set();
  const result = [];
  for (const list of lists) {
    const normalized = normalizeIdList(list) || [];
    for (const id of normalized) {
      if (seen.has(id)) continue;
      seen.add(id);
      result.push(id);
    }
  }
  return result;
}

function isMatchLocked(match) {
  const dateText = cleanString(match?.date);
  if (!dateText) return false;
  const [year, month, day] = dateText.split('-').map((value) => Number.parseInt(value, 10));
  if (!year || !month || !day) return false;
  const endsAt = new Date(year, month - 1, day, 23, 59, 59, 999);
  return endsAt.getTime() <= Date.now();
}

function assertMatchEditable(match) {
  if (isMatchLocked(match)) {
    throw new HttpError('Der Termin ist verstrichen. Das Freundschaftsspiel ist nur noch sichtbar.', 409);
  }
}

function toScheduleRow(match) {
  return {
    id: match.id,
    friendlyMatchId: match.id,
    isFriendly: true,
    date: match.date,
    time: match.time,
    homeTeam: { name: match.homeTeamName },
    guestTeam: { name: match.guestTeamName },
    location: {
      name: match.locationName || 'N/A',
      address: match.locationAddress || '',
      city: match.locationCity || '',
      zip: match.locationZip || '',
    },
    leagueDetails: { name: 'Freundschaftsspiel' },
    homeMatchPoints: match.homeMatchPoints || 0,
    guestMatchPoints: match.guestMatchPoints || 0,
    isCompleted: match.isCompleted || false,
    matchSystem: match.matchSystem,
    singlesCount: match.singlesCount,
    doublesCount: match.doublesCount,
    winningSets: match.winningSets,
    homeParticipants: normalizeParticipantList(match.homeParticipants),
    guestParticipants: normalizeParticipantList(match.guestParticipants),
    resultDetails: normalizeArrayValue(match.resultDetails),
    playersReady: normalizeArrayValue(match.playersReady),
    playersPlanned: normalizeArrayValue(match.playersPlanned),
    playersPlayed: normalizeArrayValue(match.playersPlayed),
    isLocked: isMatchLocked(match),
  };
}

class FriendlyMatchService {
  async list(userToken, clubId) {
    await checkAccess(userToken, clubId);
    const matches = await FriendlyMatch.findAll({
      where: { clubId },
      order: [['date', 'ASC'], ['time', 'ASC'], ['id', 'ASC']],
    });
    return matches.map(toScheduleRow);
  }

  async create(userToken, clubId, payload = {}) {
    await checkAccess(userToken, clubId);
    const homeTeamName = cleanString(payload.homeTeamName);
    const guestTeamName = cleanString(payload.guestTeamName);
    const date = cleanString(payload.date);
    if (!homeTeamName || !guestTeamName || !date) {
      throw new HttpError('Datum, Heimteam und Gastteam sind Pflichtfelder.', 400);
    }

    const homeParticipants = normalizeParticipantList(payload.homeParticipants);
    const guestParticipants = normalizeParticipantList(payload.guestParticipants);
    const resultDetails = Array.isArray(payload.resultDetails) ? payload.resultDetails : [];
    const autoPlayed = (Boolean(payload.isCompleted) || resultDetails.length > 0)
      ? memberIdsFromParticipants(homeParticipants, guestParticipants)
      : [];

    const match = await FriendlyMatch.create({
      clubId,
      date,
      time: cleanOptionalString(payload.time),
      homeTeamName,
      guestTeamName,
      locationName: cleanOptionalString(payload.locationName),
      locationAddress: cleanOptionalString(payload.locationAddress),
      locationCity: cleanOptionalString(payload.locationCity),
      locationZip: cleanOptionalString(payload.locationZip),
      matchSystem: cleanString(payload.matchSystem, 'Braunschweiger System'),
      singlesCount: Number.parseInt(payload.singlesCount, 10) || 12,
      doublesCount: Number.parseInt(payload.doublesCount, 10) || 4,
      winningSets: Number.parseInt(payload.winningSets, 10) || 3,
      homeMatchPoints: Number.parseInt(payload.homeMatchPoints, 10) || 0,
      guestMatchPoints: Number.parseInt(payload.guestMatchPoints, 10) || 0,
      isCompleted: Boolean(payload.isCompleted),
      homeParticipants,
      guestParticipants,
      resultDetails,
      playersReady: [],
      playersPlanned: [],
      playersPlayed: autoPlayed,
    });
    return toScheduleRow(match);
  }

  async update(userToken, clubId, matchId, payload = {}) {
    await checkAccess(userToken, clubId);
    const match = await FriendlyMatch.findOne({ where: { id: matchId, clubId } });
    if (!match) throw new HttpError('Freundschaftsspiel nicht gefunden.', 404);
    assertMatchEditable(match);

    const updates = {};
    for (const field of ['date', 'time', 'homeTeamName', 'guestTeamName', 'locationName', 'locationAddress', 'locationCity', 'locationZip', 'matchSystem']) {
      if (Object.prototype.hasOwnProperty.call(payload, field)) {
        updates[field] = field === 'date' || field === 'homeTeamName' || field === 'guestTeamName' || field === 'matchSystem'
          ? cleanString(payload[field])
          : cleanOptionalString(payload[field]);
      }
    }
    for (const field of ['singlesCount', 'doublesCount', 'winningSets', 'homeMatchPoints', 'guestMatchPoints']) {
      if (Object.prototype.hasOwnProperty.call(payload, field)) {
        updates[field] = Number.parseInt(payload[field], 10) || 0;
      }
    }
    if (Object.prototype.hasOwnProperty.call(payload, 'isCompleted')) updates.isCompleted = Boolean(payload.isCompleted);
    if (Object.prototype.hasOwnProperty.call(payload, 'homeParticipants')) updates.homeParticipants = normalizeParticipantList(payload.homeParticipants);
    if (Object.prototype.hasOwnProperty.call(payload, 'guestParticipants')) updates.guestParticipants = normalizeParticipantList(payload.guestParticipants);
    if (Object.prototype.hasOwnProperty.call(payload, 'resultDetails')) {
      updates.resultDetails = Array.isArray(payload.resultDetails) ? payload.resultDetails : [];
    }
    if (Object.prototype.hasOwnProperty.call(payload, 'resultDetails') || payload.isCompleted === true) {
      const playedParticipants = memberIdsFromParticipants(
        Object.prototype.hasOwnProperty.call(updates, 'homeParticipants') ? updates.homeParticipants : match.homeParticipants,
        Object.prototype.hasOwnProperty.call(updates, 'guestParticipants') ? updates.guestParticipants : match.guestParticipants,
      );
      updates.playersPlayed = mergeIdLists(match.playersPlayed, playedParticipants);
    }

    await match.update(updates);
    return toScheduleRow(match);
  }

  async remove(userToken, clubId, matchId) {
    await checkAccess(userToken, clubId);
    const deleted = await FriendlyMatch.destroy({ where: { id: matchId, clubId } });
    if (!deleted) throw new HttpError('Freundschaftsspiel nicht gefunden.', 404);
    return { success: true };
  }

  async updatePlayers(userToken, clubId, matchId, payload = {}) {
    await checkAccess(userToken, clubId);
    const match = await FriendlyMatch.findOne({ where: { id: matchId, clubId } });
    if (!match) throw new HttpError('Freundschaftsspiel nicht gefunden.', 404);
    assertMatchEditable(match);

    const ready = normalizeIdList(payload.playersReady);
    const planned = normalizeIdList(payload.playersPlanned);
    const played = normalizeIdList(payload.playersPlayed);
    await match.update({
      playersReady: ready ?? (match.playersReady || []),
      playersPlanned: planned ?? (match.playersPlanned || []),
      playersPlayed: played ?? (match.playersPlayed || []),
    });
    return toScheduleRow(match);
  }

  async members(userToken, clubId) {
    await checkAccess(userToken, clubId);
    return Member.findAll({
      where: { clubId, active: true },
      attributes: ['id', 'firstName', 'lastName', 'gender'],
      order: [['lastName', 'ASC'], ['firstName', 'ASC']],
    });
  }
}

export default new FriendlyMatchService();
