package com.honey.honey.service;

import com.honey.honey.config.LocaleConfig;
import com.honey.honey.config.TelegramProperties;
import com.honey.honey.dto.RecentBonusClaimDto;
import com.honey.honey.dto.TaskDto;
import com.honey.honey.model.Task;
import com.honey.honey.model.UserA;
import com.honey.honey.model.UserB;
import com.honey.honey.model.UserD;
import com.honey.honey.model.UserTaskClaim;
import com.honey.honey.repository.TaskRepository;
import com.honey.honey.repository.UserARepository;
import com.honey.honey.repository.UserBRepository;
import com.honey.honey.repository.UserDRepository;
import com.honey.honey.repository.UserTaskClaimRepository;
import jakarta.persistence.EntityManager;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import java.util.stream.Collectors;

@Slf4j
@Service
@RequiredArgsConstructor
public class TaskService {

    private final TaskRepository taskRepository;
    private final UserTaskClaimRepository userTaskClaimRepository;
    private final UserDRepository userDRepository;
    private final UserBRepository userBRepository;
    private final UserARepository userARepository;
    private final TelegramService telegramService;
    private final TelegramProperties telegramProperties;
    private final TransactionService transactionService;
    private final EntityManager entityManager;
    private final LocalizationService localizationService;
    private final FeatureSwitchService featureSwitchService;

    /**
     * Gets all tasks for a specific type.
     * For referral tasks, includes user progress and claim status.
     * @param userId User ID to get tasks for
     * @param type Task type (referral, follow, other)
     * @param languageCode User's language code for localization (optional, defaults to EN)
     */
    public List<TaskDto> getTasksByType(Integer userId, String type, String languageCode) {
        List<Task> tasks = taskRepository.findByTypeOrderByDisplayOrderAsc(type);
        
        // Get user progress for different task types
        Integer userReferals1 = null;
        Long userDepositTotal = null;
        if ("referral".equals(type)) {
            userReferals1 = getUserReferals1(userId);
        } else if ("other".equals(type)) {
            userDepositTotal = getUserDepositTotal(userId);
        }
        
        // Get user's claimed tasks
        List<Integer> claimedTaskIds = userTaskClaimRepository.findByUserId(userId)
                .stream()
                .map(UserTaskClaim::getTaskId)
                .collect(Collectors.toList());
        
        final Integer finalUserReferals1 = userReferals1;
        final Long finalUserDepositTotal = userDepositTotal;
        final List<Integer> finalClaimedTaskIds = claimedTaskIds;
        
        return tasks.stream()
                .filter(task -> !"daily".equals(task.getType()))
                .filter(task -> !finalClaimedTaskIds.contains(task.getId()))
                .filter(task -> isReferralTaskEnabled(task))
                .map(task -> {
                    boolean isClaimed = finalClaimedTaskIds.contains(task.getId());
                    String progress = buildProgressString(task, finalUserReferals1, finalUserDepositTotal, isClaimed, languageCode);
                    Long currentValue = getCurrentValue(task, finalUserReferals1, finalUserDepositTotal);
                    
                    // Localize title and description
                    String localizedTitle = localizeTaskTitle(task, languageCode);
                    String localizedDescription = localizeTaskDescription(task, languageCode);
                    String localizedRewardText = localizeRewardText(task, languageCode);
                    
                    return TaskDto.builder()
                            .id(task.getId())
                            .type(task.getType())
                            .requirement(task.getRequirement())
                            .rewardAmount(task.getRewardAmount())
                            .rewardType(task.getRewardType())
                            .title(localizedTitle)
                            .description(localizedDescription)
                            .displayOrder(task.getDisplayOrder())
                            .claimed(isClaimed)
                            .progress(progress)
                            .currentValue(currentValue)
                            .localizedRewardText(localizedRewardText)
                            .build();
                })
                .collect(Collectors.toList());
    }

    /**
     * Returns true if the task should be shown/claimable. Referral tasks with requirement 50 or 100
     * are gated by feature switches (task_referral_50_enabled, task_referral_100_enabled).
     */
    private boolean isReferralTaskEnabled(Task task) {
        if (!"referral".equals(task.getType())) {
            return true;
        }
        long req = task.getRequirement() != null ? task.getRequirement() : 0;
        if (req == 50) {
            return featureSwitchService.isTaskReferral50Enabled();
        }
        if (req == 100) {
            return featureSwitchService.isTaskReferral100Enabled();
        }
        return true;
    }

    /**
     * Returns true if the user has claimed at least one of the referral tasks with requirement 50 or 100.
     * Used to set manual_pay=1 on crypto withdrawal requests when applicable.
     */
    @Transactional(readOnly = true)
    public boolean hasCompletedReferral50Or100(Integer userId) {
        List<Task> tasks = taskRepository.findByTypeAndRequirementIn("referral", List.of(50L, 100L));
        if (tasks.isEmpty()) {
            return false;
        }
        List<Integer> taskIds = tasks.stream().map(Task::getId).collect(Collectors.toList());
        return userTaskClaimRepository.existsByUserIdAndTaskIdIn(userId, taskIds);
    }

    /**
     * Gets user's level 1 referrals count (referals_1).
     */
    private Integer getUserReferals1(Integer userId) {
        Optional<UserD> userDOpt = userDRepository.findById(userId);
        return userDOpt.map(UserD::getReferals1).orElse(0);
    }

    /**
     * Gets user's total deposit amount (deposit_total).
     */
    private Long getUserDepositTotal(Integer userId) {
        Optional<UserB> userBOpt = userBRepository.findById(userId);
        return userBOpt.map(UserB::getDepositTotal).orElse(0L);
    }

    /**
     * Builds progress string for a task.
     * For referral tasks: shows "current / requirement" or "CLAIMED" if already claimed.
     * For other tasks and follow tasks: returns null - frontend will build progress string from currentValue and requirement.
     */
    private String buildProgressString(Task task, Integer userReferals1, Long userDepositTotal, boolean isClaimed, String languageCode) {
        if (isClaimed) {
            // Return localized "CLAIMED" text
            if (languageCode == null || languageCode.isEmpty() || "XX".equals(languageCode)) {
                languageCode = "EN";
            }
            Locale locale = LocaleConfig.languageCodeToLocale(languageCode);
            return localizationService.getMessage(locale, "task.claimed");
        }
        
        if ("referral".equals(task.getType()) && userReferals1 != null) {
            Long requirement = task.getRequirement();
            return userReferals1 + " / " + requirement;
        }
        
        // For other and follow tasks, return null - frontend will build progress from currentValue and requirement
        return null;
    }

    /**
     * Gets current value for progress calculation (in bigint format).
     * Frontend will convert to display format.
     */
    private Long getCurrentValue(Task task, Integer userReferals1, Long userDepositTotal) {
        if ("referral".equals(task.getType()) && userReferals1 != null) {
            return (long) userReferals1;
        }
        
        if ("other".equals(task.getType()) && userDepositTotal != null) {
            return userDepositTotal;
        }
        
        return null;
    }

    /**
     * Claims a task for a user.
     * Checks if task is completed, and if so, marks it as claimed and gives reward.
     * 
     * IMPORTANT: For non-daily tasks, this method prevents abuse by checking if the task is already claimed FIRST,
     * before checking completion. This ensures that even if a user leaves and rejoins a channel,
     * they cannot claim the reward multiple times. Once a task is claimed, the claim record
     * persists and prevents any further claims for that user-task combination.
     * 
     * For daily tasks, the method allows claiming again after 24 hours have passed since the last claim.
     * The old claim record is deleted and a new one is created with the current timestamp.
     * 
     * @return true if task was successfully claimed, false if task is not completed or already claimed (for non-daily tasks)
     */
    @Transactional
    public boolean claimTask(Integer userId, Integer taskId) {
        // Get task first to check its type
        Optional<Task> taskOpt = taskRepository.findById(taskId);
        if (taskOpt.isEmpty()) {
            log.warn("Task not found: taskId={}", taskId);
            return false;
        }
        
        Task task = taskOpt.get();

        // Daily bonus removed - reject daily task claims
        if ("daily".equals(task.getType())) {
            return false;
        }

        // Reject claim if this referral task (50 or 100 friends) is temporarily disabled
        if (!isReferralTaskEnabled(task)) {
            log.debug("Task disabled by feature switch: taskId={}, type={}, requirement={}", taskId, task.getType(), task.getRequirement());
            return false;
        }
        
        // Check if already claimed to prevent abuse
        if (userTaskClaimRepository.existsByUserIdAndTaskId(userId, taskId)) {
            log.debug("Task already claimed: userId={}, taskId={}", userId, taskId);
            return false;
        }
        
        // Check if task is completed
        // For daily tasks, this checks if 24 hours have passed since last claim
        if (!isTaskCompleted(userId, task)) {
            log.debug("Task not completed: userId={}, taskId={}", userId, taskId);
            return false;
        }
        
        // Save to user_task_claims table
        UserTaskClaim claim = UserTaskClaim.builder()
                .userId(userId)
                .taskId(taskId)
                .build();
        userTaskClaimRepository.save(claim);
        
        // Give reward (rewardAmount is already in bigint format)
        giveReward(userId, task.getRewardAmount());
        
        try {
            transactionService.createTaskBonusTransaction(userId, task.getRewardAmount(), taskId);
        } catch (Exception e) {
            log.error("Error creating transaction: userId={}, taskId={}, type={}", userId, taskId, task.getType(), e);
            // Continue even if transaction record creation fails
        }
        
        log.info("Task claimed: userId={}, taskId={}, reward={}", 
                userId, taskId, task.getRewardAmount());
        
        return true;
    }

    /**
     * Checks if a task is completed by the user.
     */
    private boolean isTaskCompleted(Integer userId, Task task) {
        if ("referral".equals(task.getType())) {
            Integer referals1 = getUserReferals1(userId);
            return referals1 >= task.getRequirement();
        }
        
        if ("other".equals(task.getType())) {
            // For other tasks, requirement is deposit_total threshold in bigint format
            Long depositTotal = getUserDepositTotal(userId);
            return depositTotal >= task.getRequirement();
        }
        
        if ("follow".equals(task.getType())) {
            // For follow tasks, check if user is a member of the Telegram channel
            Optional<UserA> userAOpt = userARepository.findById(userId);
            if (userAOpt.isEmpty() || userAOpt.get().getTelegramId() == null) {
                log.warn("User not found or has no telegram ID: userId={}", userId);
                return false;
            }
            
            Long telegramUserId = userAOpt.get().getTelegramId();
            // Get channel ID from configuration based on task requirement
            // requirement=1 for News channel, requirement=2 for Withdrawals channel
            String chatId;
            Long requirement = task.getRequirement();
            if (requirement != null && requirement.equals(1L)) {
                chatId = telegramProperties.getFollowTaskChannelId();
            } else if (requirement != null && requirement.equals(2L)) {
                chatId = telegramProperties.getFollowTaskChannelId2();
            } else {
                // Fallback to first channel for backward compatibility
                chatId = telegramProperties.getFollowTaskChannelId();
            }
            
            if (chatId == null || chatId.isEmpty()) {
                log.error("Follow task channel ID is not configured for requirement={}", task.getRequirement());
                return false;
            }
            
            try {
                return telegramService.isUserMemberOfChat(telegramUserId, chatId);
            } catch (IllegalStateException e) {
                // Handle Telegram API errors gracefully
                // If it's a "member not found" error, it's already handled in TelegramService
                // and returns false. Other errors should be logged but not crash the task check.
                log.warn("Error checking channel membership for follow task: userId={}, chatId={}", 
                        telegramUserId, chatId, e);
                return false;
            }
        }
        
        if ("daily".equals(task.getType())) {
            // Daily bonus removed - never completed
            return false;
        }
        
        return false;
    }

    /**
     * Gets daily bonus status for a user.
     * Returns availability status and cooldown time if on cooldown.
     */
    /** Daily bonus removed - always returns unavailable. */
    public com.honey.honey.dto.DailyBonusStatusDto getDailyBonusStatus(Integer userId) {
        return com.honey.honey.dto.DailyBonusStatusDto.builder()
                .taskId(null)
                .available(false)
                .cooldownSeconds(null)
                .rewardAmount(0L)
                .build();
    }

    /**
     * Gets the 50 most recent daily bonus claims with user information.
     * Returns claims ordered by claimed_at DESC (most recent first).
     * Simple query without JOINs - all data is in user_daily_bonus_claims table.
     * 
     * @param timezone Optional timezone (e.g., "Europe/Kiev"). If null, uses UTC.
     * @param languageCode User's language code for localization (e.g., "EN", "RU")
     * @return List of RecentBonusClaimDto with avatar URL, screen name, and formatted claim timestamp
     */
    /** Daily bonus removed - always returns empty list. */
    public List<RecentBonusClaimDto> getRecentDailyBonusClaims(String timezone, String languageCode) {
        return List.of();
    }

    /**
     * Gives reward to user's balance.
     * rewardAmount is already in bigint format from frontend.
     */
    private void giveReward(Integer userId, Long rewardAmount) {
        Optional<UserB> userBOpt = userBRepository.findById(userId);
        if (userBOpt.isEmpty()) {
            log.warn("User balance not found: userId={}", userId);
            return;
        }
        
        UserB userB = userBOpt.get();
        userB.setBalanceA(userB.getBalanceA() + rewardAmount);
        userBRepository.save(userB);
        
        log.debug("Reward given: userId={}, amount={}", userId, rewardAmount);
    }
    
    /**
     * Localizes task title based on task type and requirement.
     * Falls back to database title if no translation key matches.
     */
    private String localizeTaskTitle(Task task, String languageCode) {
        if (languageCode == null || languageCode.isEmpty() || "XX".equals(languageCode)) {
            languageCode = "EN";
        }
        
        Locale locale = LocaleConfig.languageCodeToLocale(languageCode);
        String key = null;
        
        if ("referral".equals(task.getType())) {
            // Use task-specific key based on requirement for proper grammar
            key = "task.title.inviteFriends." + task.getRequirement();
        } else if ("follow".equals(task.getType())) {
            // Pattern: "Follow our News channel" or "Follow Proof of payment channel"
            // Use requirement to distinguish: 1=News, 2=Withdrawals
            Long requirement = task.getRequirement();
            if (requirement != null && requirement.equals(1L)) {
                key = "task.title.followChannel";
            } else if (requirement != null && requirement.equals(2L)) {
                key = "task.title.followChannelWithdrawals";
            } else {
                key = "task.title.followChannel"; // Fallback
            }
        } else if ("daily".equals(task.getType())) {
            // Pattern: "Daily Bonus"
            key = "task.title.dailyBonus";
        } else if ("other".equals(task.getType())) {
            // Pattern: "Deposit {0} tickets"
            key = "task.title.deposit";
        }
        
        if (key != null) {
            try {
                if ("referral".equals(task.getType())) {
                    // For referral tasks, use task-specific key (no parameters needed)
                    return localizationService.getMessage(locale, key);
                } else if ("other".equals(task.getType())) {
                    // Convert requirement (bigint) to tickets for display
                    long tickets = task.getRequirement() / 1_000_000L;
                    return localizationService.getMessage(locale, key, tickets);
                } else {
                    return localizationService.getMessage(locale, key);
                }
            } catch (Exception e) {
                log.warn("Failed to localize task title for taskId={}, key={}, languageCode={}", 
                        task.getId(), key, languageCode, e);
            }
        }
        
        // Fallback to database title
        return task.getTitle();
    }
    
    /**
     * Localizes task description based on task type and requirement.
     * Falls back to database description if no translation key matches.
     */
    private String localizeTaskDescription(Task task, String languageCode) {
        if (languageCode == null || languageCode.isEmpty() || "XX".equals(languageCode)) {
            languageCode = "EN";
        }
        
        Locale locale = LocaleConfig.languageCodeToLocale(languageCode);
        String key = null;
        
        if ("referral".equals(task.getType())) {
            // Use task-specific key based on requirement for proper grammar
            key = "task.description.inviteFriends." + task.getRequirement();
        } else if ("follow".equals(task.getType())) {
            // Pattern: "Follow our Telegram channel" or "Follow Proof of payment channel"
            // Use requirement to distinguish: 1=News, 2=Withdrawals
            Long requirement = task.getRequirement();
            if (requirement != null && requirement.equals(1L)) {
                key = "task.description.followChannel";
            } else if (requirement != null && requirement.equals(2L)) {
                key = "task.description.followChannelWithdrawals";
            } else {
                key = "task.description.followChannel"; // Fallback
            }
        } else if ("daily".equals(task.getType())) {
            // Pattern: "Claim your daily free ticket!"
            key = "task.description.dailyBonus";
        } else if ("other".equals(task.getType())) {
            // Pattern: "Deposit {0} tickets"
            key = "task.description.deposit";
        }
        
        if (key != null) {
            try {
                if ("referral".equals(task.getType())) {
                    // For referral tasks, use task-specific key (no parameters needed)
                    return localizationService.getMessage(locale, key);
                } else if ("other".equals(task.getType())) {
                    // Convert requirement (bigint) to tickets for display
                    long tickets = task.getRequirement() / 1_000_000L;
                    return localizationService.getMessage(locale, key, tickets);
                } else {
                    return localizationService.getMessage(locale, key);
                }
            } catch (Exception e) {
                log.warn("Failed to localize task description for taskId={}, key={}, languageCode={}", 
                        task.getId(), key, languageCode, e);
            }
        }
        
        // Fallback to database description
        return task.getDescription();
    }
    
    /**
     * Localizes reward text based on task type and reward amount.
     * Returns formatted string like "+2 Билеты" or "+5 Билетов" for proper grammar.
     */
    private String localizeRewardText(Task task, String languageCode) {
        if (languageCode == null || languageCode.isEmpty() || "XX".equals(languageCode)) {
            languageCode = "EN";
        }
        
        Locale locale = LocaleConfig.languageCodeToLocale(languageCode);
        
        // Convert reward amount from bigint to tickets
        long tickets = task.getRewardAmount() / 1_000_000L;
        
        // Build key based on task type and requirement for proper grammar
        String key = null;
        if ("referral".equals(task.getType())) {
            key = "task.reward.tickets." + task.getRequirement();
        } else if ("follow".equals(task.getType())) {
            key = "task.reward.tickets.follow";
        } else if ("daily".equals(task.getType())) {
            key = "task.reward.tickets.daily";
        } else if ("other".equals(task.getType())) {
            // For other tasks, use the requirement (deposit amount) as key
            long requirementTickets = task.getRequirement() / 1_000_000L;
            key = "task.reward.tickets.other." + requirementTickets;
        }
        
        if (key != null) {
            try {
                return localizationService.getMessage(locale, key, tickets);
            } catch (Exception e) {
                log.warn("Failed to localize reward text for taskId={}, key={}, languageCode={}", 
                        task.getId(), key, languageCode, e);
            }
        }
        
        // Fallback: use generic tickets translation
        try {
            String ticketsKey = "tasks.tickets";
            String ticketsText = localizationService.getMessage(locale, ticketsKey);
            return "+" + tickets + " " + ticketsText;
        } catch (Exception e) {
            // Final fallback
            return "+" + tickets + " Tickets";
        }
    }
}

