#!/usr/bin/env ruby

require_relative 'shared_constants'
require_relative 'statistics_calculator'

# Centralized dose extraction and processing module
module DoseProcessor
  extend self

  # Use shared constants for text doses, containers, and units
  KNOWN_TEXT_DOSES = SharedConstants::DataProcessing::KNOWN_TEXT_DOSES
  QUANTITY_CONTAINERS = SharedConstants::DataProcessing::QUANTITY_CONTAINERS
  KNOWN_UNITS = SharedConstants::DataProcessing::KNOWN_UNITS

  # Extract numeric dose from dose string
  def extract_numeric_dose(dose_string, substance = nil, roa = nil)
    return nil if dose_string.nil? || dose_string.empty?
    
    # Handle the main pattern: "123mg" or "123.45mg"
    if dose_string =~ SharedConstants::DataProcessing::DOSE_PATTERN
      return $1.to_f
    end
    
    # Handle other common patterns
    dose_string = dose_string.to_s.strip
    
    
    # Handle prefixed with ~: "~123mg" (but only if there's a known unit)
    if dose_string =~ /^~?(\d+(?:\.\d+)?)_?([a-zA-Zµμ]+)$/
      value = $1.to_f
      unit = $2
      
      # Only proceed if it's a known unit
      if KNOWN_UNITS.include?(unit)
        # Convert µg/μg/ug/mcg to mg
        if %w[µg μg ug mcg].include?(unit)
          value /= 1000.0
        end
        
        return value
      end
    end
    
    # Handle compound doses like "1can monster", "2bottles beer"
    if dose_string =~ /^(\d+(?:\.\d+)?)([a-zA-Z]+)\s+([a-zA-Z]+)(?:\s+.*)?$/
      quantity = $1.to_f
      container = $2.downcase
      substance = $3.downcase
      
      # Check if container is a known quantity container and substance is known
      if QUANTITY_CONTAINERS.include?(container) && KNOWN_TEXT_DOSES.include?(substance)
        return quantity
      end
    end
    
    # Handle known text-only doses (return 1.0 for counting)
    if dose_string =~ /^([a-zA-Z\-_]+)$/ && KNOWN_TEXT_DOSES.include?(dose_string.downcase)
      return 1.0
    end
    
    # Handle ranges: "10-20mg", "10+20mg", or "10or20mg"
    if dose_string =~ /^(\d+)(?:[-+]|or)(\d+(?:\.\d+)?)([a-zA-Z]+)?$/
      value = ($1.to_f + $2.to_f) / 2.0
      unit = $3 || 'mg'
      
      # Convert units if needed
      if %w[µg μg ug mcg].include?(unit)
        value /= 1000.0
      end
      
      return value
    end
    
    # Handle suffixed doses: "something_123mg"
    if dose_string =~ /^.+_(\d+)([a-zA-Z]+)?$/
      value = $1.to_f
      unit = $2 || 'mg'
      
      # Convert units if needed
      if %w[µg μg ug mcg].include?(unit)
        value /= 1000.0
      end
      
      return value
    end
    
    # Handle decimal prefix: ".5mg"
    if dose_string.start_with?('.')
      dose_string = '0' + dose_string
      return extract_numeric_dose(dose_string, substance, roa)
    end
    
    # Handle comma as decimal separator
    if dose_string.include?(',')
      dose_string = dose_string.sub(',', '.')
      return extract_numeric_dose(dose_string, substance, roa)
    end
    
    nil
  end

  # Check if a dose string contains a measurable amount
  def measurable_dose?(dose_string, substance = nil, roa = nil)
    !extract_numeric_dose(dose_string, substance, roa).nil?
  end

  # Calculate average dose with validation
  def calculate_average_dose(doses, measurable_count, total_count)
    return nil if doses.empty?
    
    # Only calculate average if we have numeric doses
    numeric_doses = doses.compact
    return nil if numeric_doses.empty?
    
    avg = (numeric_doses.sum / numeric_doses.length).round(1)
    
    # Only show average if most doses are measurable to avoid misleading stats
    threshold = SharedConstants::DataProcessing::DOSE_THRESHOLD
    return nil if measurable_count < total_count * threshold
    
    avg
  end

  # Process dose data for monthly summary
  def process_monthly_doses(entries)
    monthly = {}
    
    entries.each do |entry|
      next unless entry[:timestamp] && entry[:substance]
      
      month_key = entry[:timestamp].strftime('%Y-%m')
      substance = entry[:substance]
      
      # Initialize nested structure
      monthly[month_key] ||= {}
      monthly[month_key][substance] ||= { 
        count: 0, 
        doses: [], 
        measurable_count: 0 
      }
      
      # Increment count
      monthly[month_key][substance][:count] += 1
      
      # Extract and store numeric dose
      numeric_dose = extract_numeric_dose(entry[:dose], entry[:substance], entry[:roa])
      if numeric_dose
        monthly[month_key][substance][:doses] << numeric_dose
        monthly[month_key][substance][:measurable_count] += 1
      end
    end
    
    # Calculate averages
    monthly.each do |month, substances|
      substances.each do |substance, data|
        data[:avg_dose] = calculate_average_dose(
          data[:doses], 
          data[:measurable_count], 
          data[:count]
        )
      end
    end
    
    monthly
  end

  # Classify dose strength based on statistical analysis
  def classify_dose_strength(dose_value, stats)
    return :unknown if dose_value.nil? || stats.nil?
    
    mean = stats[:clean_mean]
    std_dev = stats[:std_dev]
    
    return :unknown if mean.nil? || std_dev.nil? || std_dev.zero?
    
    # Calculate z-score
    z_score = (dose_value - mean) / std_dev
    
    case z_score
    when -Float::INFINITY...-1.5
      :very_light
    when -1.5...-0.5
      :light
    when -0.5...0.5
      :common
    when 0.5...1.5
      :strong
    when 1.5...2.5
      :heavy
    else
      :extreme
    end
  end

  # Get dose classification thresholds
  def get_dose_thresholds(doses)
    return {} if doses.empty?
    
    sorted_doses = doses.sort
    
    {
      p25: StatisticsCalculator.percentile(sorted_doses, 25),
      p50: StatisticsCalculator.percentile(sorted_doses, 50),
      p75: StatisticsCalculator.percentile(sorted_doses, 75),
      p90: StatisticsCalculator.percentile(sorted_doses, 90),
      p95: StatisticsCalculator.percentile(sorted_doses, 95),
      mean: doses.sum / doses.length,
      std_dev: calculate_std_dev(doses)
    }
  end

  # Validate dose format
  def valid_dose_format?(dose_string)
    return false if dose_string.nil? || dose_string.empty?
    
    # Trim whitespace for validation
    trimmed_dose = dose_string.strip
    return false if trimmed_dose.empty?
    
    # Check against common patterns
    basic_patterns = [
      SharedConstants::DataProcessing::DOSE_PATTERN,
      /^.+_(\d+)([a-zA-Z]+)$/,  # Require unit for suffixed format
      /^([1-9])$/  # Single digits 1-9 for counting doses like "1 beer"
    ]
    
    # Check basic numeric patterns first
    if basic_patterns.any? { |pattern| trimmed_dose.match?(pattern) }
      return true
    end
    
    # Check unit-based patterns with semantic validation
    if trimmed_dose =~ /^~?(\d+(?:\.\d+)?)_?([a-zA-Zµμ]+)$/
      unit = $2
      if KNOWN_UNITS.include?(unit)
        return true
      end
    end
    
    # Check known text-only doses
    if trimmed_dose =~ /^([a-zA-Z\-_]+)$/ && KNOWN_TEXT_DOSES.include?(trimmed_dose.downcase)
      return true
    end
    
    # Check compound doses 
    result = extract_numeric_dose(trimmed_dose)
    if result && result > 0
      return true
    end
    
    false
  end

  private


  # Calculate standard deviation
  def calculate_std_dev(values)
    return 0 if values.length < 2
    
    mean = values.sum / values.length
    variance = values.map { |x| (x - mean) ** 2 }.sum / (values.length - 1)
    Math.sqrt(variance)
  end
end
