import fs from 'fs';
import csv from 'csv-parser';
import { parse } from 'date-fns';
import iconv from 'iconv-lite';
import Match from '../models/Match.js';
import Season from '../models/Season.js';
import Location from '../models/Location.js';
import League from '../models/League.js';
import Team from '../models/Team.js';
import ClubTeam from '../models/ClubTeam.js';
import Club from '../models/Club.js';
import SeasonService from './seasonService.js';
import { checkAccess } from '../utils/userUtils.js';
import { Op } from 'sequelize';
import HttpError from '../exceptions/HttpError.js';

import { devLog } from '../utils/logger.js';
class MatchService {

    /**
     * Format team name with age class suffix
     * @param {string} teamName - Base team name (e.g. "Harheimer TC")
     * @param {string} ageClass - Age class (e.g. "Jugend 11", "Senioren", "Frauen", "Erwachsene")
     * @returns {string} Formatted team name (e.g. "Harheimer TC (J11)")
     */
    formatTeamNameWithAgeClass(teamName, ageClass) {
        if (!ageClass || ageClass.trim() === '' || ageClass === 'Erwachsene') {
            return teamName;
        }

        // Parse age class
        const ageClassLower = ageClass.toLowerCase().trim();
        
        // Senioren = S
        if (ageClassLower.includes('senioren')) {
            return `${teamName} (S)`;
        }
        
        // Frauen = F
        if (ageClassLower.includes('frauen')) {
            return `${teamName} (F)`;
        }
        
        // Jugend XX = JXX
        const jugendMatch = ageClass.match(/jugend\s+(\d+)/i);
        if (jugendMatch) {
            return `${teamName} (J${jugendMatch[1]})`;
        }
        
        // Mädchen XX = MXX
        const maedchenMatch = ageClass.match(/m[aä]dchen\s+(\d+)/i);
        if (maedchenMatch) {
            return `${teamName} (M${maedchenMatch[1]})`;
        }
        
        // Default: return as is
        return teamName;
    }

    generateSeasonString(date = new Date()) {
        const currentYear = date.getFullYear();
        let seasonStartYear;
        if (date >= new Date(currentYear, 6, 1)) {
            seasonStartYear = currentYear;
        } else {
            seasonStartYear = currentYear - 1;
        }
        const seasonEndYear = seasonStartYear + 1; 
        return `${seasonStartYear}/${seasonEndYear}`;
    }

    normalizeTeamNameForMatch(name) {
        return String(name || '')
            .normalize('NFD')
            .replace(/[\u0300-\u036f]/g, '')
            .toLowerCase()
            .replace(/\s*\((?:j|m)\d+\)\s*/gi, ' ')
            .replace(/\s+/g, ' ')
            .trim();
    }

    getAgeGroupCode(ageClass) {
        const match = String(ageClass || '').match(/jugend\s+(\d+)/i);
        return match ? `J${match[1]}` : null;
    }

    async linkYouthClubTeamsToImportedLeagues(clubId, seasonId, leaguesByAgeGroup) {
        if (!leaguesByAgeGroup.size) {
            return;
        }

        const clubTeams = await ClubTeam.findAll({
            where: { clubId, seasonId, teamAgeGroup: { [Op.ne]: 'adult' } }
        });

        for (const clubTeam of clubTeams) {
            const leagueId = leaguesByAgeGroup.get(clubTeam.teamAgeGroup);
            if (leagueId && clubTeam.leagueId !== leagueId) {
                await clubTeam.update({ leagueId });
            }
        }
    }

    async findExistingMatchForCSV(clubId, date, homeTeamName, guestTeamName) {
        if (Number.isNaN(date?.getTime?.())) {
            return null;
        }

        const startOfDay = new Date(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0, 0);
        const endOfDay = new Date(date.getFullYear(), date.getMonth(), date.getDate(), 23, 59, 59, 999);
        const candidates = await Match.findAll({
            where: {
                clubId,
                date: { [Op.between]: [startOfDay, endOfDay] }
            },
            attributes: ['id', 'homeTeamId', 'guestTeamId', 'leagueId', 'locationId']
        });

        const normalizedHome = this.normalizeTeamNameForMatch(homeTeamName);
        const normalizedGuest = this.normalizeTeamNameForMatch(guestTeamName);

        for (const candidate of candidates) {
            const [homeTeam, guestTeam] = await Promise.all([
                Team.findByPk(candidate.homeTeamId, { attributes: ['name'] }),
                Team.findByPk(candidate.guestTeamId, { attributes: ['name'] })
            ]);
            const candidateHome = this.normalizeTeamNameForMatch(homeTeam?.name);
            const candidateGuest = this.normalizeTeamNameForMatch(guestTeam?.name);

            if (candidateHome === normalizedHome && candidateGuest === normalizedGuest) {
                return candidate;
            }
        }

        return null;
    }

    async importCSV(userToken, clubId, filePath) { 
        await checkAccess(userToken, clubId); 
        let seasonString = '';
        const matches = [];
        const importedYouthLeagues = new Map();
        try { 
            const fileStream = fs.createReadStream(filePath)
                // click-TT exports this file as Windows-1252 / ISO-8859-1.
                // Decoding it as UTF-8 corrupts umlauts in venue names and addresses.
                .pipe(iconv.decodeStream('win1252'))
                .pipe(csv({ separator: ';' }));
            for await (const row of fileStream) {
                const parsedDate = parse(row['Termin'], 'dd.MM.yyyy HH:mm', new Date());
                seasonString = this.generateSeasonString(parsedDate);
                const [season] = await Season.findOrCreate({
                    where: { season: seasonString },
                });
                const [league] = await League.findOrCreate({
                    where: {
                        name: row['Staffel'],
                        clubId: clubId,
                        seasonId: season.id,
                    },
                });
                if (row['Runde'] !== 'Pokal') {
                    const ageGroup = this.getAgeGroupCode(row['Altersklasse']);
                    if (ageGroup) {
                        importedYouthLeagues.set(ageGroup, league.id);
                    }
                }
                const homeTeamName = this.formatTeamNameWithAgeClass(
                    row['HeimMannschaft'],
                    row['HeimMannschaftAltersklasse']
                );
                const guestTeamName = this.formatTeamNameWithAgeClass(
                    row['GastMannschaft'],
                    row['GastMannschaftAltersklasse']
                );
                let location = null;
                if (row['HalleName']?.trim()) {
                    [location] = await Location.findOrCreate({
                        where: {
                            name: row['HalleName'],
                            address: row['HalleStrasse'],
                            city: row['HalleOrt'],
                            zip: row['HallePLZ'],
                        },
                    });
                }

                // Club teams already configured through MyTischtennis can be linked to
                // leagues with a different display name than the click-TT CSV "Staffel".
                // Update the existing match in that league so the schedule view receives
                // the venue instead of creating a disconnected duplicate in a CSV league.
                const existingMatch = await this.findExistingMatchForCSV(
                    clubId,
                    parsedDate,
                    homeTeamName,
                    guestTeamName
                );
                if (existingMatch) {
                    if (location && existingMatch.locationId !== location.id) {
                        await existingMatch.update({ locationId: location.id });
                    }
                    continue;
                }

                const homeTeamId = await this.getOrCreateTeamId(
                    row['HeimMannschaft'], 
                    row['HeimMannschaftAltersklasse'],
                    clubId,
                    league.id,
                    season.id
                );
                const guestTeamId = await this.getOrCreateTeamId(
                    row['GastMannschaft'],
                    row['GastMannschaftAltersklasse'],
                    clubId,
                    league.id,
                    season.id
                );
                matches.push({
                    date: parsedDate,
                    time: row['Termin'].split(' ')[1],
                    homeTeamId: homeTeamId,
                    guestTeamId: guestTeamId,
                    leagueId: league.id,
                    locationId: location?.id || null,
                    clubId: clubId,
                });
            }
            let season = null;
            if (seasonString) {
                season = await Season.findOne({ where: { season: seasonString } });
            }
            const result = await Match.bulkCreate(matches);
            if (season) {
                await this.linkYouthClubTeamsToImportedLeagues(
                    clubId,
                    season.id,
                    importedYouthLeagues
                );
            }
            return result;

        } catch (error) {
            devLog('Error during CSV import:', error);
            throw error;
        }
    }

    async getOrCreateTeamId(teamName, ageClass, clubId, leagueId, seasonId) {
        // Format team name with age class
        const formattedTeamName = this.formatTeamNameWithAgeClass(teamName, ageClass);
        
        devLog(`Team: "${teamName}" + "${ageClass}" -> "${formattedTeamName}"`);
        
        const [team] = await Team.findOrCreate({
            where: {
                name: formattedTeamName,
                clubId: clubId,
                leagueId: leagueId,
                seasonId: seasonId
            },
            defaults: {
                name: formattedTeamName,
                clubId: clubId,
                leagueId: leagueId,
                seasonId: seasonId
            }
        });
        return team.id;
    }


    async getLeaguesForCurrentSeason(userToken, clubId, seasonId = null) {
        await checkAccess(userToken, clubId);
        
        // Verwende SeasonService für korrekte Saison-Verwaltung
        let season;
        if (!seasonId) {
            season = await SeasonService.getOrCreateCurrentSeason();
        } else {
            season = await SeasonService.getSeasonById(seasonId);
            if (!season) {
                throw new Error('Season not found');
            }
        }
        
        try {
            const leagues = await League.findAll({
                where: {
                    clubId: clubId,
                    seasonId: season.id
                },
                attributes: ['id', 'name'],
                order: [['name', 'ASC']]
            });
            return leagues;
        } catch (error) {
            console.error('Error retrieving leagues:', error);
            throw new Error('Failed to retrieve leagues');
        }
    }

    async getMatchesForLeagues(userToken, clubId, seasonId = null) {
        await checkAccess(userToken, clubId);
        
        // Wenn keine Saison angegeben, verwende die aktuelle
        let season;
        if (!seasonId) {
            season = await SeasonService.getOrCreateCurrentSeason();
        } else {
            season = await SeasonService.getSeasonById(seasonId);
            if (!season) {
                throw new Error('Season not found');
            }
        }

        // Nur Spiele mit eigener Mannschaft (gleiche Logik wie getMatchesForLeague mit scope "own"):
        // Mannschaften, deren Name mit dem Vereinsnamen beginnt, in Ligen dieser Saison.
        const club = await Club.findByPk(clubId, { attributes: ['name'] });
        const leaguesInSeason = await League.findAll({
            where: { clubId, seasonId: season.id },
            attributes: ['id']
        });
        const leagueIdList = leaguesInSeason.map((l) => l.id);
        let ownTeamIdSet = new Set();
        if (club?.name && leagueIdList.length) {
            const escaped = String(club.name).replace(/\\/g, '\\\\').replace(/%/g, '\\%').replace(/_/g, '\\_');
            const ownTeams = await Team.findAll({
                where: {
                    leagueId: { [Op.in]: leagueIdList },
                    name: { [Op.like]: `${escaped}%` }
                },
                attributes: ['id']
            });
            ownTeamIdSet = new Set(ownTeams.map((t) => t.id));
        }

        const matches = await Match.findAll({
            where: {
                clubId: clubId,
            }
        });
        
        // Filtere Matches nach Liga-Saison und lade Daten manuell
        const enrichedMatches = [];
        for (const match of matches) {
            // Lade Liga-Daten
            const league = await League.findByPk(match.leagueId, { attributes: ['name', 'seasonId'] });
            if (!league || league.seasonId !== season.id) {
                continue; // Skip matches from other seasons
            }

            // Kalender: nur Punktspiele, an denen der Verein beteiligt ist (eigene Mannschaft Heim oder Gast)
            if (ownTeamIdSet.size > 0) {
                if (!ownTeamIdSet.has(match.homeTeamId) && !ownTeamIdSet.has(match.guestTeamId)) {
                    continue;
                }
            }
            
            const enrichedMatch = {
                id: match.id,
                date: match.date,
                time: match.time,
                homeTeamId: match.homeTeamId,
                guestTeamId: match.guestTeamId,
                locationId: match.locationId,
                leagueId: match.leagueId,
                code: match.code,
                homePin: match.homePin,
                guestPin: match.guestPin,
                homeMatchPoints: match.homeMatchPoints || 0,
                guestMatchPoints: match.guestMatchPoints || 0,
                isCompleted: match.isCompleted || false,
                pdfUrl: match.pdfUrl,
                playersReady: match.playersReady || [],
                playersPlanned: match.playersPlanned || [],
                playersPlayed: match.playersPlayed || [],
                homeTeam: { name: 'Unbekannt' },
                guestTeam: { name: 'Unbekannt' },
                location: { name: 'Unbekannt', address: '', city: '', zip: '' },
                leagueDetails: { name: league.name }
            };
            
            if (match.homeTeamId) {
                const homeTeam = await Team.findByPk(match.homeTeamId, { attributes: ['name'] });
                if (homeTeam) enrichedMatch.homeTeam = homeTeam;
            }
            if (match.guestTeamId) {
                const guestTeam = await Team.findByPk(match.guestTeamId, { attributes: ['name'] });
                if (guestTeam) enrichedMatch.guestTeam = guestTeam;
            }
            if (match.locationId) {
                const location = await Location.findByPk(match.locationId, { 
                    attributes: ['name', 'address', 'city', 'zip'] 
                });
                if (location) enrichedMatch.location = location;
            }
            
            enrichedMatches.push(enrichedMatch);
        }
        return enrichedMatches;
    }

    async getMatchesForLeague(userToken, clubId, leagueId, scope = 'own') {
        await checkAccess(userToken, clubId);
        const seasonString = this.generateSeasonString();
        const season = await Season.findOne({
            where: {
                season: {
                    [Op.like]: `%${seasonString}%`
                }
            }
        });
        if (!season) {
            throw new Error('Season not found');
        }
        
        // Get club name from database
        const club = await Club.findByPk(clubId, { attributes: ['name'] });
        if (!club) {
            throw new Error('Club not found');
        }
        let matches;
        if (scope === 'all') {
            matches = await Match.findAll({
                where: {
                    leagueId: leagueId
                }
            });
        } else {
            const clubName = club.name;

            const ownTeams = await Team.findAll({
                where: {
                    name: {
                        [Op.like]: `${clubName}%`
                    },
                    leagueId: leagueId
                },
                attributes: ['id', 'name']
            });

            const ownTeamIds = ownTeams.map(t => t.id);

            if (ownTeamIds.length > 0) {
                matches = await Match.findAll({
                    where: {
                        leagueId: leagueId,
                        [Op.or]: [
                            { homeTeamId: { [Op.in]: ownTeamIds } },
                            { guestTeamId: { [Op.in]: ownTeamIds } }
                        ]
                    }
                });
            } else {
                matches = [];
            }
        }
        
        // Lade Team- und Location-Daten manuell
        const enrichedMatches = [];
        for (const match of matches) {
            const enrichedMatch = {
                id: match.id,
                date: match.date,
                time: match.time,
                homeTeamId: match.homeTeamId,
                guestTeamId: match.guestTeamId,
                locationId: match.locationId,
                leagueId: match.leagueId,
                code: match.code,
                homePin: match.homePin,
                guestPin: match.guestPin,
                homeMatchPoints: match.homeMatchPoints || 0,
                guestMatchPoints: match.guestMatchPoints || 0,
                isCompleted: match.isCompleted || false,
                pdfUrl: match.pdfUrl,
                playersReady: match.playersReady || [],
                playersPlanned: match.playersPlanned || [],
                playersPlayed: match.playersPlayed || [],
                homeTeam: { name: 'Unbekannt' },
                guestTeam: { name: 'Unbekannt' },
                location: { name: 'Unbekannt', address: '', city: '', zip: '' },
                leagueDetails: { name: 'Unbekannt' }
            };
            
            if (match.homeTeamId) {
                const homeTeam = await Team.findByPk(match.homeTeamId, { attributes: ['name'] });
                if (homeTeam) enrichedMatch.homeTeam = homeTeam;
            }
            if (match.guestTeamId) {
                const guestTeam = await Team.findByPk(match.guestTeamId, { attributes: ['name'] });
                if (guestTeam) enrichedMatch.guestTeam = guestTeam;
            }
            if (match.locationId) {
                const location = await Location.findByPk(match.locationId, { 
                    attributes: ['name', 'address', 'city', 'zip'] 
                });
                if (location) enrichedMatch.location = location;
            }
            if (match.leagueId) {
                const league = await League.findByPk(match.leagueId, { attributes: ['name'] });
                if (league) enrichedMatch.leagueDetails = league;
            }
            
            enrichedMatches.push(enrichedMatch);
        }
        return enrichedMatches;
    }

    /**
     * Get league table for a specific league
     * @param {string} userToken - User authentication token
     * @param {string} clubId - Club ID
     * @param {string} leagueId - League ID
     * @returns {Array} League table data
     */
    async getLeagueTable(userToken, clubId, leagueId) {
        await checkAccess(userToken, clubId);

        try {
            // Get all teams in this league
            const teams = await Team.findAll({
                where: {
                    leagueId: leagueId
                },
                attributes: [
                    'id', 'name', 'matchesPlayed', 'matchesWon', 'matchesLost', 'matchesTied',
                    'setsWon', 'setsLost', 'pointsWon', 'pointsLost', 'tablePoints', 'tablePointsWon', 'tablePointsLost'
                ],
                order: [
                    ['tablePointsWon', 'DESC'], // Highest table points first
                    ['matchesWon', 'DESC'],     // Then by matches won
                    ['setsWon', 'DESC']         // Then by sets won
                ]
            });

            // Format table data
            const tableData = teams.map(team => {
                return {
                    teamId: team.id,
                    teamName: team.name,
                    setsWon: team.setsWon,
                    setsLost: team.setsLost,
                    matchPoints: team.matchesWon + ':' + team.matchesLost,
                    tablePoints: team.tablePointsWon + ':' + team.tablePointsLost, // Tabellenpunkte (points_won:points_lost)
                    pointRatio: team.pointsWon + ':' + team.pointsLost // Ballpunkte (games_won:games_lost)
                };
            });

            return tableData;
        } catch (error) {
            console.error('Error getting league table:', error);
            throw new Error('Failed to get league table');
        }
    }

    async updateMatchPlayers(userToken, matchId, playersReady, playersPlanned, playersPlayed) {
        // Find the match and verify access
        const match = await Match.findByPk(matchId, {
            include: [
                { model: Club, as: 'club' }
            ]
        });
        
        if (!match) {
            throw new HttpError('Match not found', 404);
        }
        
        await checkAccess(userToken, match.clubId);
        
        const normalizeList = (list) => {
            if (!Array.isArray(list)) return null;
            const seen = new Set();
            const result = [];
            for (const value of list) {
                if (value === null || value === undefined) continue;
                const key = String(value);
                if (seen.has(key)) continue;
                seen.add(key);
                result.push(value);
            }
            return result;
        };

        const readyList = normalizeList(playersReady);
        const plannedList = normalizeList(playersPlanned);
        const playedList = normalizeList(playersPlayed);

        // Wenn Listen übergeben wurden, gelten sie als Quelle der Wahrheit
        await match.update({
            playersReady: readyList !== null ? readyList : (match.playersReady || []),
            playersPlanned: plannedList !== null ? plannedList : (match.playersPlanned || []),
            playersPlayed: playedList !== null ? playedList : (match.playersPlayed || [])
        });

        // Aktualisiertes Match nochmals laden und für WebSocket-Broadcast anreichern (gleiche Struktur wie getMatchesForLeague)
        const updated = await Match.findByPk(matchId);
        if (!updated) {
            throw new HttpError('Match not found after update', 404);
        }
        const enriched = {
            id: updated.id,
            date: updated.date,
            time: updated.time,
            homeTeamId: updated.homeTeamId,
            guestTeamId: updated.guestTeamId,
            locationId: updated.locationId,
            leagueId: updated.leagueId,
            code: updated.code,
            homePin: updated.homePin,
            guestPin: updated.guestPin,
            homeMatchPoints: updated.homeMatchPoints || 0,
            guestMatchPoints: updated.guestMatchPoints || 0,
            isCompleted: updated.isCompleted || false,
            pdfUrl: updated.pdfUrl,
            playersReady: updated.playersReady || [],
            playersPlanned: updated.playersPlanned || [],
            playersPlayed: updated.playersPlayed || [],
            homeTeam: { name: 'Unbekannt' },
            guestTeam: { name: 'Unbekannt' },
            location: { name: 'Unbekannt', address: '', city: '', zip: '' },
            leagueDetails: { name: 'Unbekannt' }
        };
        if (updated.homeTeamId) {
            const homeTeam = await Team.findByPk(updated.homeTeamId, { attributes: ['name'] });
            if (homeTeam) enriched.homeTeam = homeTeam;
        }
        if (updated.guestTeamId) {
            const guestTeam = await Team.findByPk(updated.guestTeamId, { attributes: ['name'] });
            if (guestTeam) enriched.guestTeam = guestTeam;
        }
        if (updated.locationId) {
            const location = await Location.findByPk(updated.locationId, { attributes: ['name', 'address', 'city', 'zip'] });
            if (location) enriched.location = location;
        }
        if (updated.leagueId) {
            const league = await League.findByPk(updated.leagueId, { attributes: ['name'] });
            if (league) enriched.leagueDetails = league;
        }

        return {
            id: updated.id,
            clubId: updated.clubId,
            playersReady: updated.playersReady,
            playersPlanned: updated.playersPlanned,
            playersPlayed: updated.playersPlayed,
            match: enriched
        };
    }

    async getPlayerMatchStats(userToken, clubId, leagueId, seasonId) {
        await checkAccess(userToken, clubId);
        
        // Get all matches for this league/season
        const matches = await Match.findAll({
            where: {
                clubId: clubId,
                leagueId: leagueId
            },
            attributes: ['id', 'date', 'playersPlayed']
        });
        
        // Get all members
        const Member = (await import('../models/Member.js')).default;
        const members = await Member.findAll({
            where: { clubId: clubId, active: true },
            attributes: ['id', 'firstName', 'lastName']
        });
        
        // Calculate stats
        const stats = {};
        const now = new Date();
        
        // Saison startet am 1. Juli
        const seasonStart = new Date();
        seasonStart.setMonth(6, 1); // 1. Juli
        seasonStart.setHours(0, 0, 0, 0);
        if (seasonStart > now) {
            seasonStart.setFullYear(seasonStart.getFullYear() - 1);
        }
        
        // Vorrunde: 1. Juli bis 31. Dezember
        const firstHalfEnd = new Date(seasonStart.getFullYear(), 11, 31, 23, 59, 59, 999); // 31. Dezember
        
        // Rückrunde startet am 1. Januar (im Jahr nach Saisonstart)
        const secondHalfStart = new Date(seasonStart.getFullYear() + 1, 0, 1, 0, 0, 0, 0); // 1. Januar
        
        for (const member of members) {
            stats[member.id] = {
                memberId: member.id,
                firstName: member.firstName,
                lastName: member.lastName,
                totalSeason: 0,
                totalFirstHalf: 0,
                totalSecondHalf: 0
            };
        }
        
        for (const match of matches) {
            // Parse playersPlayed if it's a JSON string
            let playersPlayed = match.playersPlayed;
            if (typeof playersPlayed === 'string') {
                try {
                    playersPlayed = JSON.parse(playersPlayed);
                } catch (e) {
                    continue;
                }
            }
            
            if (!playersPlayed || !Array.isArray(playersPlayed) || playersPlayed.length === 0) {
                continue;
            }
            
            const matchDate = new Date(match.date);
            const isInSeason = matchDate >= seasonStart;
            const isInFirstHalf = matchDate >= seasonStart && matchDate <= firstHalfEnd;
            const isInSecondHalf = matchDate >= secondHalfStart;
            
            for (const playerId of playersPlayed) {
                if (stats[playerId]) {
                    if (isInSeason) stats[playerId].totalSeason++;
                    if (isInFirstHalf) stats[playerId].totalFirstHalf++;
                    if (isInSecondHalf) stats[playerId].totalSecondHalf++;
                }
            }
        }
        
        // Convert to array, filter out players with 0 matches, and sort by totalSeason descending
        return Object.values(stats)
            .filter(player => player.totalSeason > 0)
            .sort((a, b) => b.totalSeason - a.totalSeason);
    }

}

export default new MatchService();
