import { BaseService } from '../../../core/interfaces/IService';
import { IRewardService } from './interfaces/IRewardService';
import { IRewardRepository } from '../repositories/interfaces/IRewardRepository';
import { IEventBus } from '../../../core/events/EventBus';
import {
  ErrorHandler,
  NotFoundError,
  ValidationError,
  RewardAlreadyProcessedError,
  BusinessLogicError,
  ServiceError
} from '../../../core/errors';
import {
  Reward,
  RewardType,
  RewardSource,
  RewardCalculationInput,
  RewardCalculationResult,
  RewardConfig,
  RewardStatus,
  defaultRewardConfig,
  LearningHoursReward,
  PerformanceBonusReward,
  CompletionReward,
  ConsistencyReward
} from '../types/reward.types';

/**
 * Request interface for creating a new reward
 */
interface CreateRewardRequest {
  userId: string;
  type: RewardType;
  amount: number;
  description: string;
  metadata?: Record<string, any>;
}

/**
 * Reward service implementation
 */
export class RewardService extends BaseService implements IRewardService {
  readonly id = 'reward-service';
  readonly name = 'Participant Reward Service';
  
  private rewardRepository: IRewardRepository;
  private eventBus: IEventBus;
  private config: RewardConfig = { ...defaultRewardConfig };
  private errorHandler: ErrorHandler;

  constructor(rewardRepository: IRewardRepository, eventBus: IEventBus) {
    super();
    this.rewardRepository = rewardRepository;
    this.eventBus = eventBus;
    this.errorHandler = ErrorHandler.getInstance();
  }

  async initialize(): Promise<void> {
    await super.initialize();
    // Load existing rewards and configuration from storage
    await this.loadRewardsFromStorage();
    await this.loadConfigFromStorage();
    console.log('RewardService initialized');
  }

  async calculateReward(input: RewardCalculationInput): Promise<RewardCalculationResult> {
    switch (input.type) {
      case RewardType.LEARNING_HOURS:
        return this.calculateLearningHoursReward(
          input.userId,
          input.metadata.hoursLogged,
          input.metadata.sessionIds
        );
      case RewardType.PERFORMANCE_BONUS:
        return this.calculatePerformanceBonusReward(
          input.userId,
          input.metadata.performanceScore,
          input.metadata.achievementId
        );
      case RewardType.COMPLETION_REWARD:
        return this.calculateCompletionReward(
          input.userId,
          input.metadata.completedItemId,
          input.metadata.completedItemType,
          input.metadata.score
        );
      case RewardType.CONSISTENCY_REWARD:
        return this.calculateConsistencyReward(
          input.userId,
          input.metadata.streakDays,
          input.metadata.streakType
        );
      default:
        throw new Error(`Unsupported reward type: ${input.type}`);
    }
  }

  async createReward(request: CreateRewardRequest): Promise<Reward> {
    try {
      // Validation
      this.validateCreateRewardRequest(request);

      // Create base reward properties
      const baseReward = {
        id: `reward_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
        userId: request.userId,
        type: request.type,
        source: RewardSource.LEARNING_TIME, // Default source, should be passed in request
        amount: request.amount,
        currency: 'IDR',
        status: RewardStatus.PENDING,
        description: request.description,
        metadata: request.metadata || {},
        createdAt: new Date(),
        updatedAt: new Date()
      };

      // Create type-specific reward based on the reward type
      let reward: Reward;
      switch (request.type) {
        case RewardType.LEARNING_HOURS:
          reward = {
            ...baseReward,
            type: RewardType.LEARNING_HOURS,
            hoursLogged: request.metadata?.hoursLogged || 0,
            ratePerHour: request.metadata?.ratePerHour || this.config.learningHours.ratePerHour,
            sessionIds: request.metadata?.sessionIds || []
          } as LearningHoursReward;
          break;
        case RewardType.PERFORMANCE_BONUS:
          reward = {
            ...baseReward,
            type: RewardType.PERFORMANCE_BONUS,
            performanceScore: request.metadata?.performanceScore || 0,
            bonusMultiplier: request.metadata?.bonusMultiplier || 1,
            achievementId: request.metadata?.achievementId || ''
          } as PerformanceBonusReward;
          break;
        case RewardType.COMPLETION_REWARD:
          reward = {
            ...baseReward,
            type: RewardType.COMPLETION_REWARD,
            completedItemId: request.metadata?.completedItemId || '',
            completedItemType: request.metadata?.completedItemType || 'course',
            completionScore: request.metadata?.completionScore
          } as CompletionReward;
          break;
        case RewardType.CONSISTENCY_REWARD:
          reward = {
            ...baseReward,
            type: RewardType.CONSISTENCY_REWARD,
            streakDays: request.metadata?.streakDays || 0,
            streakType: request.metadata?.streakType || 'daily_login',
            bonusMultiplier: request.metadata?.bonusMultiplier || 1
          } as ConsistencyReward;
          break;
        default:
          throw new Error(`Unsupported reward type: ${request.type}`);
      }

      const createdReward = await this.rewardRepository.create(reward);

      // Emit event
      await this.eventBus.emit('reward.created', {
        rewardId: createdReward.id,
        userId: createdReward.userId,
        type: createdReward.type,
        amount: createdReward.amount
      });

      return createdReward;
    } catch (error) {
      this.errorHandler.handleError(error as Error, { 
        userId: request.userId,
        metadata: {
          rewardType: request.type,
          amount: request.amount,
          operation: 'createReward',
          service: 'RewardService'
        }
      });
      throw error;
    }
  }

  async getUserRewards(userId: string): Promise<Reward[]> {
    try {
      if (!userId?.trim()) {
        throw new ValidationError('User ID is required', 'userId', userId);
      }

      const rewards = await this.rewardRepository.findByUserId(userId);
      return rewards;
    } catch (error) {
      this.errorHandler.handleError(error as Error, { 
        userId, 
        metadata: {
          operation: 'getUserRewards',
          service: 'RewardService'
        }
      });
      throw error;
    }
  }

  async getRewardById(id: string): Promise<Reward | null> {
    try {
      if (!id?.trim()) {
        throw new ValidationError('Reward ID is required', 'id', id);
      }

      const reward = await this.rewardRepository.findById(id);
      if (!reward) {
        throw new NotFoundError('Reward', id);
      }
      
      return reward;
    } catch (error) {
      this.errorHandler.handleError(error as Error, { 
        metadata: {
          rewardId: id, 
          operation: 'getRewardById',
          service: 'RewardService'
        }
      });
      throw error;
    }
  }

  async getRewardsByUserId(
    userId: string,
    filters?: {
      type?: RewardType;
      status?: RewardStatus;
      dateFrom?: Date;
      dateTo?: Date;
      limit?: number;
      offset?: number;
    }
  ): Promise<Reward[]> {
    let userRewards = await this.rewardRepository.findByUserId(userId);

    if (filters) {
      if (filters.type) {
        userRewards = userRewards.filter(r => r.type === filters.type);
      }
      if (filters.status) {
        userRewards = userRewards.filter(r => r.status === filters.status);
      }
      if (filters.dateFrom) {
        userRewards = userRewards.filter(r => r.createdAt >= filters.dateFrom!);
      }
      if (filters.dateTo) {
        userRewards = userRewards.filter(r => r.createdAt <= filters.dateTo!);
      }
    }

    // Sort by creation date (newest first)
    userRewards.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());

    // Apply pagination
    if (filters?.offset) {
      userRewards = userRewards.slice(filters.offset);
    }
    if (filters?.limit) {
      userRewards = userRewards.slice(0, filters.limit);
    }

    return userRewards;
  }

  async getRewardsByStatus(status: RewardStatus, limit?: number, offset?: number): Promise<Reward[]> {
    // Use findPendingRewardsForPayroll as a workaround since we don't have findByStatus
    let rewards = await this.rewardRepository.findPendingRewardsForPayroll(new Date(0), new Date());
    rewards = rewards.filter(r => r.status === status);
    
    // Sort by creation date (newest first)
    rewards.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());

    if (offset) {
      rewards = rewards.slice(offset);
    }
    if (limit) {
      rewards = rewards.slice(0, limit);
    }

    return rewards;
  }

  async updateRewardStatus(id: string, status: RewardStatus, approvedBy?: string): Promise<Reward> {
    const reward = await this.rewardRepository.findById(id);
    if (!reward) {
      throw new Error(`Reward not found: ${id}`);
    }

    const updateData = {
      status,
      updatedAt: new Date(),
      ...(status === RewardStatus.APPROVED && approvedBy ? { approvedBy, approvedAt: new Date() } : {}),
      ...(status === RewardStatus.PAID ? { paidAt: new Date() } : {})
    };

    const updatedReward = await this.rewardRepository.update(id, updateData);

    // Emit event
    await this.eventBus.emit('reward.status.updated', {
      rewardId: id,
      userId: reward.userId,
      oldStatus: reward.status,
      newStatus: status,
      updatedBy: approvedBy,
      updatedAt: updatedReward.updatedAt
    });

    return updatedReward;
  }

  async approveReward(id: string, approvedBy: string): Promise<Reward> {
    try {
      if (!id?.trim()) {
        throw new ValidationError('Reward ID is required', 'id', id);
      }
      if (!approvedBy?.trim()) {
        throw new ValidationError('Approver ID is required', 'approvedBy', approvedBy);
      }

      const reward = await this.rewardRepository.findById(id);
      if (!reward) {
        throw new NotFoundError('Reward', id);
      }

      if (reward.status !== RewardStatus.PENDING) {
        throw new RewardAlreadyProcessedError(id, reward.status);
      }

      const updatedReward = await this.rewardRepository.update(id, {
        status: RewardStatus.APPROVED,
        approvedBy,
        approvedAt: new Date(),
        updatedAt: new Date()
      });

      // Emit event
      await this.eventBus.emit('reward.approved', {
        rewardId: id,
        userId: reward.userId,
        approvedBy,
        amount: reward.amount
      });

      return updatedReward;
    } catch (error) {
      this.errorHandler.handleError(error as Error, { 
        metadata: {
          rewardId: id,
          approvedBy,
          operation: 'approveReward',
          service: 'RewardService'
        }
      });
      throw error;
    }
  }

  async rejectReward(id: string, reason: string, rejectedBy: string): Promise<Reward> {
    const reward = await this.updateRewardStatus(id, RewardStatus.REJECTED, rejectedBy);
    
    // Add rejection reason to metadata
    const updatedReward = await this.rewardRepository.update(id, {
      metadata: {
        ...reward.metadata,
        rejectionReason: reason,
        rejectedBy,
        rejectedAt: new Date()
      }
    });

    return updatedReward;
  }

  async markRewardAsPaid(id: string, transactionId: string): Promise<Reward> {
    const reward = await this.updateRewardStatus(id, RewardStatus.PAID);
    
    // Add transaction reference
    const updatedReward = await this.rewardRepository.update(id, {
      metadata: {
        ...reward.metadata,
        transactionId,
        paidAt: new Date()
      }
    });

    return updatedReward;
  }

  async getUserRewardStatistics(userId: string, dateFrom?: Date, dateTo?: Date) {
    const userRewards = await this.getRewardsByUserId(userId, { dateFrom, dateTo });
    
    const totalEarned = userRewards
      .filter(r => r.status === RewardStatus.PAID)
      .reduce((sum, r) => sum + r.amount, 0);
    
    const totalPending = userRewards
      .filter(r => r.status === RewardStatus.PENDING)
      .reduce((sum, r) => sum + r.amount, 0);
    
    const totalApproved = userRewards
      .filter(r => r.status === RewardStatus.APPROVED)
      .reduce((sum, r) => sum + r.amount, 0);
    
    const totalPaid = totalEarned;

    const rewardBreakdown = Object.values(RewardType).map(type => {
      const typeRewards = userRewards.filter(r => r.type === type);
      return {
        type,
        count: typeRewards.length,
        totalAmount: typeRewards.reduce((sum, r) => sum + r.amount, 0)
      };
    });

    return {
      totalEarned,
      totalPending,
      totalApproved,
      totalPaid,
      rewardBreakdown
    };
  }

  async getRewardConfig(): Promise<RewardConfig> {
    return { ...this.config };
  }

  async updateRewardConfig(config: Partial<RewardConfig>): Promise<RewardConfig> {
    this.config = { ...this.config, ...config };
    await this.saveConfigToStorage();
    return { ...this.config };
  }

  async calculateLearningHoursReward(userId: string, hoursLogged: number, sessionIds: string[]): Promise<RewardCalculationResult> {
    const { learningHours } = this.config;
    
    if (!learningHours.enabled) {
      throw new Error('Learning hours rewards are disabled');
    }

    const cappedHours = Math.min(hoursLogged, learningHours.maxHoursPerDay);
    const amount = cappedHours * learningHours.ratePerHour;

    return {
      amount,
      currency: learningHours.currency,
      description: `Learning hours reward: ${cappedHours} hours at ${learningHours.ratePerHour} per hour`,
      metadata: {
        hoursLogged: cappedHours,
        ratePerHour: learningHours.ratePerHour,
        sessionIds
      }
    };
  }

  async calculatePerformanceBonusReward(userId: string, performanceScore: number, achievementId: string): Promise<RewardCalculationResult> {
    const { performanceBonus } = this.config;
    
    if (!performanceBonus.enabled) {
      throw new Error('Performance bonus rewards are disabled');
    }

    let multiplier = performanceBonus.multipliers.average;
    if (performanceScore >= 90) {
      multiplier = performanceBonus.multipliers.excellent;
    } else if (performanceScore >= 80) {
      multiplier = performanceBonus.multipliers.good;
    }

    const amount = performanceBonus.baseAmount * multiplier;

    return {
      amount,
      currency: performanceBonus.currency,
      description: `Performance bonus: ${performanceScore}% score with ${multiplier}x multiplier`,
      metadata: {
        performanceScore,
        bonusMultiplier: multiplier,
        achievementId,
        baseAmount: performanceBonus.baseAmount
      }
    };
  }

  async calculateCompletionReward(userId: string, completedItemId: string, completedItemType: 'course' | 'exam' | 'module', score?: number): Promise<RewardCalculationResult> {
    const { completionReward } = this.config;
    
    if (!completionReward.enabled) {
      throw new Error('Completion rewards are disabled');
    }

    let amount: number;
    switch (completedItemType) {
      case 'course':
        amount = completionReward.courseCompletion;
        break;
      case 'exam':
        amount = completionReward.examPass;
        break;
      case 'module':
        amount = completionReward.moduleCompletion;
        break;
      default:
        throw new Error(`Unsupported completion type: ${completedItemType}`);
    }

    return {
      amount,
      currency: completionReward.currency,
      description: `Completion reward for ${completedItemType}: ${completedItemId}`,
      metadata: {
        completedItemId,
        completedItemType,
        completionScore: score
      }
    };
  }

  async calculateConsistencyReward(userId: string, streakDays: number, streakType: 'daily_login' | 'daily_learning' | 'weekly_goals'): Promise<RewardCalculationResult> {
    const { consistencyReward } = this.config;
    
    if (!consistencyReward.enabled) {
      throw new Error('Consistency rewards are disabled');
    }

    let amount: number;
    let multiplier = 1;

    if (streakType === 'daily_login' || streakType === 'daily_learning') {
      const weeks = Math.floor(streakDays / 7);
      multiplier = Math.min(
        1 + (weeks * consistencyReward.dailyStreak.multiplierPerWeek),
        consistencyReward.dailyStreak.maxMultiplier
      );
      amount = consistencyReward.dailyStreak.baseAmount * multiplier;
    } else {
      amount = consistencyReward.weeklyGoals.baseAmount * consistencyReward.weeklyGoals.multiplier;
    }

    return {
      amount,
      currency: consistencyReward.currency,
      description: `Consistency reward: ${streakDays} day streak (${streakType})`,
      metadata: {
        streakDays,
        streakType,
        bonusMultiplier: multiplier
      }
    };
  }

  async bulkCreateRewards(rewards: Array<Omit<Reward, 'id' | 'createdAt' | 'updatedAt'>>): Promise<Reward[]> {
    const createdRewards: Reward[] = [];
    
    for (const reward of rewards) {
      const created = await this.createReward(reward);
      createdRewards.push(created);
    }

    return createdRewards;
  }

  async getPendingRewardsCount(): Promise<number> {
    const rewards = await this.rewardRepository.findPendingRewardsForPayroll(new Date(0), new Date());
    return rewards.filter(r => r.status === RewardStatus.PENDING).length;
  }

  async getTotalRewardsByPeriod(startDate: Date, endDate: Date, status?: RewardStatus) {
    // Use findPendingRewardsForPayroll as a workaround to get rewards by date range
    let rewards = await this.rewardRepository.findPendingRewardsForPayroll(startDate, endDate);

    if (status) {
      rewards = rewards.filter(r => r.status === status);
    }

    const totalAmount = rewards.reduce((sum, r) => sum + r.amount, 0);
    const currency = rewards.length > 0 ? 'IDR' : 'IDR'; // Default to IDR since Reward doesn't have currency property

    return {
      totalAmount,
      count: rewards.length,
      currency
    };
  }

  private validateCreateRewardRequest(request: CreateRewardRequest): void {
    if (!request.userId?.trim()) {
      throw new ValidationError('User ID is required', 'userId', request.userId);
    }

    if (!request.type || !Object.values(RewardType).includes(request.type)) {
      throw new ValidationError('Valid reward type is required', 'type', request.type);
    }

    if (typeof request.amount !== 'number' || request.amount <= 0) {
      throw new ValidationError('Amount must be a positive number', 'amount', request.amount);
    }

    if (!request.description?.trim()) {
      throw new ValidationError('Description is required', 'description', request.description);
    }

    // Business rule validations - check against appropriate config limits
    let maxAmount = 0;
    switch (request.type) {
      case RewardType.LEARNING_HOURS:
        maxAmount = this.config.learningHours.ratePerHour * this.config.learningHours.maxHoursPerDay;
        break;
      case RewardType.PERFORMANCE_BONUS:
        maxAmount = this.config.performanceBonus.baseAmount * this.config.performanceBonus.multipliers.excellent;
        break;
      case RewardType.COMPLETION_REWARD:
        maxAmount = Math.max(
          this.config.completionReward.courseCompletion,
          this.config.completionReward.examPass,
          this.config.completionReward.moduleCompletion
        );
        break;
      case RewardType.CONSISTENCY_REWARD:
        maxAmount = this.config.consistencyReward.dailyStreak.baseAmount * this.config.consistencyReward.dailyStreak.maxMultiplier;
        break;
      default:
        maxAmount = 1000000; // Default max amount
    }
    
    if (request.amount > maxAmount) {
      throw new BusinessLogicError(
        `Reward amount ${request.amount} exceeds maximum allowed ${maxAmount} for type ${request.type}`,
        'max_reward_amount_exceeded'
      );
    }
  }

  private generateId(): string {
    return `reward_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
  }

  private async loadRewardsFromStorage(): Promise<void> {
    // In a real implementation, this would load from a database
    // For now, we'll start with an empty map
  }

  private async loadConfigFromStorage(): Promise<void> {
    // In a real implementation, this would load from a database or config file
    // For now, we'll use the default config
  }

  private async saveConfigToStorage(): Promise<void> {
    // In a real implementation, this would save to a database or config file
  }
}