import { Server } from 'socket.io'
import Logger, { LoggerType } from '#application/logger'
import worldRepository from '#repositories/worldRepository'
import worldService from '#services/worldService'
import SocketManager from '#managers/socketManager'

type WeatherState = {
  isRainEnabled: boolean
  rainPercentage: number
  isFogEnabled: boolean
  fogDensity: number
}

class WeatherManager {
  private static readonly CONFIG = {
    UPDATE_INTERVAL_MS: 60_000, // Check weather every minute
    RAIN_CHANCE: 0.2, // 20% chance
    FOG_CHANCE: 0.15, // 15% chance
    RAIN_PERCENTAGE_RANGE: { min: 50, max: 100 },
    FOG_DENSITY_RANGE: { min: 30, max: 100 }
  } as const

  private readonly logger = Logger.type(LoggerType.APP)
  private io: Server | null = null
  private intervalId: NodeJS.Timeout | null = null

  private weatherState: WeatherState = {
    isRainEnabled: false,
    rainPercentage: 0,
    isFogEnabled: false,
    fogDensity: 0
  }

  public async boot(): Promise<void> {
    this.io = SocketManager.getIO()
    await this.loadWeather()
    this.startWeatherLoop()
    this.logger.info('Weather manager loaded')
  }

  public getWeatherState(): WeatherState {
    return { ...this.weatherState }
  }

  public async toggleRain(): Promise<void> {
    this.updateWeatherProperty('rain')
    await this.saveAndEmitWeather()
  }

  public async toggleFog(): Promise<void> {
    this.updateWeatherProperty('fog')
    await this.saveAndEmitWeather()
  }

  public cleanup(): void {
    this.intervalId && clearInterval(this.intervalId)
  }

  private async loadWeather(): Promise<void> {
    try {
      const world = await worldRepository.getFirst()
      if (world) {
        this.weatherState = {
          isRainEnabled: world.isRainEnabled,
          rainPercentage: world.rainPercentage,
          isFogEnabled: world.isFogEnabled,
          fogDensity: world.fogDensity
        }
      }
    } catch (error) {
      this.logError('load', error)
    }
  }

  private startWeatherLoop(): void {
    this.intervalId = setInterval(async () => {
      this.updateRandomWeather()
      await this.saveAndEmitWeather()
    }, WeatherManager.CONFIG.UPDATE_INTERVAL_MS)
  }

  private updateRandomWeather(): void {
    if (Math.random() < WeatherManager.CONFIG.RAIN_CHANCE) {
      this.updateWeatherProperty('rain')
    }
    if (Math.random() < WeatherManager.CONFIG.FOG_CHANCE) {
      this.updateWeatherProperty('fog')
    }
  }

  private updateWeatherProperty(type: 'rain' | 'fog'): void {
    if (type === 'rain') {
      this.weatherState.isRainEnabled = !this.weatherState.isRainEnabled
      this.weatherState.rainPercentage = this.weatherState.isRainEnabled
        ? this.getRandomNumber(
          WeatherManager.CONFIG.RAIN_PERCENTAGE_RANGE.min,
          WeatherManager.CONFIG.RAIN_PERCENTAGE_RANGE.max
        )
        : 0
    }

    if (type === 'fog') {
      this.weatherState.isFogEnabled = !this.weatherState.isFogEnabled
      this.weatherState.fogDensity = this.weatherState.isFogEnabled
        ? this.getRandomNumber(
          WeatherManager.CONFIG.FOG_DENSITY_RANGE.min,
          WeatherManager.CONFIG.FOG_DENSITY_RANGE.max
        )
        : 0
    }
  }

  private async saveAndEmitWeather(): Promise<void> {
    await this.saveWeather()
    this.emitWeather()
  }

  private emitWeather(): void {
    this.io?.emit('weather', this.weatherState)
  }

  private async saveWeather(): Promise<void> {
    try {
      await worldService.update(this.weatherState)
    } catch (error) {
      this.logError('save', error)
    }
  }

  private getRandomNumber(min: number, max: number): number {
    return Math.floor(Math.random() * (max - min + 1)) + min
  }

  private logError(operation: string, error: unknown): void {
    this.logger.error(
      `Failed to ${operation} weather: ${error instanceof Error ? error.message : String(error)}`
    )
  }
}

export default new WeatherManager()