import { IStorage } from "./storage";
import { User, InsertXpEvent } from "@shared/schema";
import { getLevelFromXP } from "@shared/gamification";

// Constants for XP Actions
export const XP_RULES = {
    CREATE_TASK: 10,
    CREATE_SUBTASK: 5,
    UPDATE_TASK: 2, // Small amount for tweaking/updating
    COMPLETE_TASK: 50,
    COMPLETE_TASK_LATE: 20, // Reduced for overdue
    AI_ACTION: 5, // For using AI features
    LOGIN_STREAK: 100
};

export class GamificationService {
    private storage: IStorage;

    constructor(storage: IStorage) {
        this.storage = storage;
    }

    async awardXP(userId: string, source: string, amount?: number, details?: any): Promise<{ user: User, levelUp: boolean, oldLevel: number, newLevel: number }> {
        const user = await this.storage.getUser(userId);
        if (!user) throw new Error("User not found");

        const xpAmount = amount || this.getXPForSource(source);
        // FIX: The previous bug was likely in updateUserXP implementation in storage. 
        // Let's check storage.ts. 
        // DbStorage.updateUserXP does: .set({ xp: user.xp + xp }) where input is 'xp'. 
        // So validation: 
        // If I pass '10', DbStorage adds 10. 
        // The MemStorage implementation was: user.xp += xp; 
        // The issue: In the previous code:
        // const newTotalXP = (user.xp || 0) + xpAmount;
        // await this.storage.updateUserXP(userId, newTotalXP);
        // If user had 100 XP, xpAmount 50. newTotalXP = 150.
        // If storage.updateUserXP(150) adds 150 to 100, result is 250. 
        // If storage.updateUserXP(150) sets it to 150, result is 150.
        // MEMORY storage ADDS. DB storage ADDS.
        // "set({ xp: user.xp + xp })" -> logic implies input is DELTA.
        // So passing 'newTotalXP' (150) as delta ADDS 150. Double counting!

        // CORRECTION: Pass ONLY the delta (xpAmount).
        await this.storage.updateUserXP(userId, xpAmount);

        // Fetch fresh user to get calculated new total
        const updatedUserRaw = await this.storage.getUser(userId);
        const currentXP = updatedUserRaw?.xp || 0;

        // Check for level up
        const oldLevel = getLevelFromXP(user.xp || 0);
        const newLevel = getLevelFromXP(currentXP);
        const levelUp = newLevel > oldLevel;

        // Log Event
        await this.storage.logXpEvent({
            userId,
            amount: xpAmount,
            source,
            details // Log details
        });

        return {
            user: updatedUserRaw!,
            levelUp,
            oldLevel,
            newLevel
        };
    }

    private getXPForSource(source: string): number {
        switch (source) {
            case 'create_task': return XP_RULES.CREATE_TASK;
            case 'create_subtask': return XP_RULES.CREATE_SUBTASK;
            case 'update_task': return XP_RULES.UPDATE_TASK;
            case 'complete_task': return XP_RULES.COMPLETE_TASK;
            case 'complete_task_late': return XP_RULES.COMPLETE_TASK_LATE;
            case 'ai_action': return XP_RULES.AI_ACTION;
            case 'daily_streak': return XP_RULES.LOGIN_STREAK;
            default: return 0;
        }
    }

    async checkStreakBonuses(userId: string, currentStreak: number) {
        // Weekly Bonus (every 7 days)
        if (currentStreak > 0 && currentStreak % 7 === 0) {
            await this.awardXP(userId, 'weekly_streak_bonus', 300, { streak: currentStreak });
        }

        // Monthly Bonus (every 30 days)
        if (currentStreak > 0 && currentStreak % 30 === 0) {
            await this.awardXP(userId, 'monthly_streak_bonus', 1000, { streak: currentStreak });
        }
    }

    // Analytics Methods
    async getWeeklyAnalytics(userId: string) {
        // Return last 7 days details
        // In a real app we would use SQL aggregation.
        // For now, let's fetch events and aggregate in memory or rely on a new storage method if needed.
        // But better is to just fetch last 7 days events via storage.getXpEvents and process.
        const events = await this.storage.getXpEvents(userId);
        const now = new Date();
        const days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
        const last7Days = Array.from({ length: 7 }, (_, i) => {
            const d = new Date();
            d.setDate(now.getDate() - (6 - i));
            return d;
        });

        // Map: Label -> XP
        const data = last7Days.map(date => {
            const dayEvents = events.filter(e => {
                if (!e.createdAt) return false;
                const d = new Date(e.createdAt);
                return d.getDate() === date.getDate() && d.getMonth() === date.getMonth();
            });
            const total = dayEvents.reduce((sum, e) => sum + e.amount, 0);
            return {
                labelKey: days[date.getDay()].toLowerCase(), // 'sun', 'mon', etc. for translation
                xp: total
            };
        });
        return data; // [ { labelKey: 'mon', xp: 50 }, ... ]
    }

    async getMonthlyAnalytics(userId: string) {
        // Return 4 weeks
        const events = await this.storage.getXpEvents(userId);

        // Group by ISO Week? Or just simplified chunks.
        // Let's do 4 previous weeks based on current date.

        // Helper to get week number
        const getWeek = (d: Date) => {
            const onejan = new Date(d.getFullYear(), 0, 1);
            const millis = d.getTime() - onejan.getTime();
            return Math.ceil((((millis / 86400000) + onejan.getDay() + 1) / 7));
        };

        const currentWeek = getWeek(new Date());
        const weeks = [currentWeek - 3, currentWeek - 2, currentWeek - 1, currentWeek];

        const data = weeks.map(w => {
            const weekEvents = events.filter(e => {
                if (!e.createdAt) return false;
                const d = new Date(e.createdAt);
                return getWeek(d) === w && d.getFullYear() === new Date().getFullYear();
            });
            const total = weekEvents.reduce((sum, e) => sum + e.amount, 0);
            return {
                labelKey: w.toString(),
                xp: total
            };
        });
        return data;
    }

    async getYearlyAnalytics(userId: string) {
        const events = await this.storage.getXpEvents(userId);
        const months = ['jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec'];

        const data = months.map((m, index) => {
            const monthEvents = events.filter(e => {
                if (!e.createdAt) return false;
                const d = new Date(e.createdAt);
                return d.getMonth() === index && d.getFullYear() === new Date().getFullYear();
            });
            const total = monthEvents.reduce((sum, e) => sum + e.amount, 0);
            return {
                labelKey: m,
                xp: total
            };
        });
        return data;
    }
}
