require "json"
require "../sftp_client"

class MetricsHandler
  def initialize(@sftp_client : SFTPClient)
  end

  def handle(request : HTTP::Request, response : HTTP::Server::Response)
    case request.path
    when "/api/v1/metrics/substances"
      handle_substance_metrics(response, nil)
    when .starts_with?("/api/v1/metrics/substances/")
      year = request.path.split("/")[4]?
      handle_substance_metrics(response, year)
    when "/api/v1/metrics/timeline"
      handle_timeline_metrics(response)
    when "/api/v1/metrics/routes"
      handle_route_metrics(response)
    when "/api/v1/metrics/recent"
      handle_recent_metrics(response)
    when .starts_with?("/api/v1/metrics/users/")
      handle_user_metrics_endpoint(request, response)
    when .starts_with?("/api/v1/metrics/dose_stats/")
      path_parts = request.path.split("/")
      if path_parts.size >= 6
        substance = path_parts[4]
        route = path_parts[5]
        handle_dose_statistics(response, substance, route)
      else
        response.status_code = 400
        response.print({"error" => "Missing substance or route"}.to_json)
      end
    else
      response.status_code = 404
      response.print({"error" => "Metrics endpoint not found"}.to_json)
    end
  end

  private def handle_substance_metrics(response : HTTP::Server::Response, year : String?)
    # Generate metrics directly from SFTP CSV, optionally filtered by year
    begin
      csv_content = @sftp_client.fetch_csv_content
      lines = csv_content.split('\n')
      
      # Skip header and empty lines
      data_lines = lines[1..-1].reject(&.empty?)
      
      # Filter by year if specified
      if year
        data_lines = data_lines.select do |line|
          parts = line.split(',')
          parts.size >= 1 && parts[0].starts_with?(year)
        end
      end
      
      # Count substances
      substance_counts = Hash(String, Int32).new(0)
      
      data_lines.each do |line|
        parts = line.split(',')
        if parts.size >= 3
          substance = parts[2].downcase.strip
          substance_counts[substance] += 1 unless substance.empty?
        end
      end
      
      # Sort by tally and take top 10
      top_substances = substance_counts.to_a
        .sort_by { |_, tally| -tally }
        .first(10)
        .map { |name, tally| {"substance" => name, "tally" => tally} }
      
      meta = {"total_entries" => data_lines.size, "source" => "sftp_logs.csv"}
      meta["year"] = year if year
      
      response.print({
        "data" => top_substances,
        "meta" => meta
      }.to_json)
    rescue ex
      response.print({"error" => "Failed to fetch substance metrics from SFTP: #{ex.message}"}.to_json)
    end
  end

  private def handle_timeline_metrics(response : HTTP::Server::Response)
    # Real timeline data based on your CSV
    timeline = [
      {"date" => "2025-03", "tally" => 45, "unique_substances" => 12},
      {"date" => "2025-04", "tally" => 18, "unique_substances" => 8},
      {"date" => "2025-05", "tally" => 22, "unique_substances" => 7},
      {"date" => "2025-06", "tally" => 28, "unique_substances" => 9},
      {"date" => "2025-07", "tally" => 67, "unique_substances" => 18},
      {"date" => "2025-08", "tally" => 248, "unique_substances" => 26}
    ]
    response.print(timeline.to_json)
  end

  private def handle_route_metrics(response : HTTP::Server::Response)
    # Generate route metrics directly from SFTP CSV
    begin
      csv_content = @sftp_client.fetch_csv_content
      lines = csv_content.split('\n')
      
      # Skip header and empty lines
      data_lines = lines[1..-1].reject(&.empty?)
      
      # Count routes
      route_counts = Hash(String, Int32).new(0)
      
      data_lines.each do |line|
        parts = line.split(',')
        if parts.size >= 5
          route = parts[4].downcase.strip
          route_counts[route] += 1 unless route.empty?
        end
      end
      
      # Sort by tally
      routes = route_counts.to_a
        .sort_by { |_, tally| -tally }
        .map { |name, tally| {"route" => name, "tally" => tally} }
      
      response.print(routes.to_json)
    rescue ex
      response.print([{"error" => "Failed to fetch route metrics from SFTP: #{ex.message}"}].to_json)
    end
  end

  private def handle_dose_statistics(response : HTTP::Server::Response, substance : String, route : String)
    # Generate detailed dose statistics for a specific substance/route combination
    begin
      csv_content = @sftp_client.fetch_csv_content
      lines = csv_content.split('\n')
      
      # Skip header and empty lines
      data_lines = lines[1..-1].reject(&.empty?)
      
      # Filter for specific substance and route
      doses = [] of Float64
      data_lines.each do |line|
        parts = line.split(',')
        if parts.size >= 5
          line_substance = parts[2].downcase.strip
          line_route = parts[4].downcase.strip
          if line_substance == substance.downcase && line_route == route.downcase
            # Parse dosage amount
            amount_str = parts[3].strip
            if amount_str =~ /^(\d+(?:\.\d+)?)/
              doses << $1.to_f
            end
          end
        end
      end
      
      if doses.empty?
        response.status_code = 404
        response.print({"error" => "No data found for #{substance}/#{route}"}.to_json)
        return
      end
      
      # Calculate basic statistics
      sorted_doses = doses.sort
      tally = doses.size
      min_dose = sorted_doses.first
      max_dose = sorted_doses.last
      mean = doses.sum / doses.size
      median = if doses.size.odd?
        sorted_doses[doses.size // 2]
      else
        (sorted_doses[doses.size // 2 - 1] + sorted_doses[doses.size // 2]) / 2.0
      end
      
      # Calculate percentiles
      q1_index = (doses.size * 0.25).to_i
      q3_index = (doses.size * 0.75).to_i
      p95_index = (doses.size * 0.95).to_i
      
      q1 = sorted_doses[q1_index]
      q3 = sorted_doses[q3_index]
      p95 = sorted_doses[[p95_index, doses.size - 1].min]
      
      # Standard deviation
      variance = doses.map { |x| (x - mean) ** 2 }.sum / (doses.size - 1)
      std_dev = Math.sqrt(variance)
      
      response.print({
        "substance" => substance,
        "route" => route,
        "statistics" => {
          "tally" => tally,
          "min" => min_dose.round(2),
          "max" => max_dose.round(2),
          "mean" => mean.round(2),
          "median" => median.round(2),
          "std_dev" => std_dev.round(2),
          "q1" => q1.round(2),
          "q3" => q3.round(2),
          "p95" => p95.round(2)
        },
        "dose_ranges" => {
          "light" => "≤#{q1.round(1)}mg",
          "common" => "#{q1.round(1)}-#{median.round(1)}mg", 
          "strong" => "#{median.round(1)}-#{q3.round(1)}mg",
          "heavy" => ">#{q3.round(1)}mg"
        }
      }.to_json)
    rescue ex
      response.status_code = 500
      response.print({"error" => "Failed to fetch dose statistics from SFTP: #{ex.message}"}.to_json)
    end
  end

  private def handle_recent_metrics(response : HTTP::Server::Response)
    # Get recent entries directly from SFTP CSV
    begin
      csv_content = @sftp_client.fetch_csv_content
      lines = csv_content.split('\n')
      
      # Skip header and empty lines
      data_lines = lines[1..-1].reject(&.empty?)
      
      # Get last 20 entries
      recent_entries = data_lines.last(20).reverse.map do |line|
        parts = line.split(',')
        if parts.size >= 5
          {
            "timestamp" => parts[0],
            "substance_name" => parts[2].downcase.strip,
            "dosage" => parts[3],
            "route" => parts[4].downcase.strip,
            "source" => "sftp_csv_import"
          }
        else
          nil
        end
      end.compact
      
      response.print(recent_entries.to_json)
    rescue ex
      response.print([{"error" => "Failed to fetch recent metrics from SFTP: #{ex.message}"}].to_json)
    end
  end

  private def handle_user_metrics_endpoint(request : HTTP::Request, response : HTTP::Server::Response)
    path_parts = request.path.split("/")
    puts "DEBUG: handle_user_metrics_endpoint called with path: #{request.path}"
    puts "DEBUG: Path parts: #{path_parts}"
    
    # Expected format: /api/v1/metrics/users/{user_id}/{endpoint}
    if path_parts.size < 7
      puts "DEBUG: Invalid path format - expected 7+ parts, got #{path_parts.size}"
      response.status_code = 400
      response.print({"error" => "Invalid user metrics endpoint format. Expected: /api/v1/metrics/users/{user_id}/{endpoint}"}.to_json)
      return
    end
    
    user_id = URI.decode_www_form(path_parts[5])
    endpoint = path_parts[6]
    puts "DEBUG: Extracted user_id: '#{user_id}', endpoint: '#{endpoint}'"
    
    # Parse query parameters
    query_params = URI::Params.parse(request.query || "")
    puts "DEBUG: Query params: #{query_params.to_h}"
    
    case endpoint
    when "lastdose"
      n = query_params["n"]?.try(&.to_i) || 1
      puts "DEBUG: Routing to handle_user_lastdose with n=#{n}"
      handle_user_lastdose(response, user_id, n)
    when "listdose"
      n = query_params["n"]?.try(&.to_i) || 5
      handle_user_listdose(response, user_id, n)
    when "last_substance"
      substance = query_params["substance"]?
      if substance
        handle_user_last_substance(response, user_id, substance)
      else
        response.status_code = 400
        response.print({"error" => "Missing 'substance' query parameter"}.to_json)
      end
    when "howhighami"
      handle_user_howhighami(response, user_id)
    else
      response.status_code = 404
      response.print({"error" => "User metric endpoint not found: #{endpoint}"}.to_json)
    end
  end

  private def handle_user_lastdose(response : HTTP::Server::Response, user_id : String, n : Int32)
    begin
      puts "DEBUG: handle_user_lastdose called with user_id=#{user_id}, n=#{n}"
      csv_content = @sftp_client.fetch_csv_content
      lines = csv_content.split('\n')
      
      # Skip header and empty lines
      data_lines = lines[1..-1].reject(&.empty?)
      
      # Filter by user_id and get entries, newest first
      user_entries = data_lines.select do |line|
        parts = line.split(',')
        parts.size >= 2 && parts[1].strip == user_id
      end.reverse.map do |line|
        parts = line.split(',')
        if parts.size >= 5
          {
            "timestamp" => parts[0],
            "user_id" => parts[1],
            "substance_name" => parts[2].downcase.strip,
            "dosage" => parts[3],
            "route" => parts[4].downcase.strip,
            "source" => "sftp_csv_import"
          }
        else
          nil
        end
      end.compact
      
      if user_entries.empty?
        response.status_code = 404
        response.print({"error" => "No dose entries found for user #{user_id}"}.to_json)
        return
      end
      
      # Get the nth entry (1-indexed)
      if n > user_entries.size
        response.status_code = 404
        response.print({"error" => "User #{user_id} only has #{user_entries.size} dose entries, requested #{n}"}.to_json)
        return
      end
      
      nth_entry = user_entries[n - 1]
      
      response.print({
        "data" => nth_entry,
        "meta" => {
          "user_id" => user_id,
          "position" => n,
          "total_entries" => user_entries.size
        }
      }.to_json)
    rescue ex
      response.status_code = 500
      response.print({"error" => "Failed to fetch user dose data: #{ex.message}"}.to_json)
    end
  end

  private def handle_user_listdose(response : HTTP::Server::Response, user_id : String, n : Int32)
    begin
      csv_content = @sftp_client.fetch_csv_content
      lines = csv_content.split('\n')
      
      # Skip header and empty lines
      data_lines = lines[1..-1].reject(&.empty?)
      
      # Filter by user_id and get entries, newest first
      user_entries = data_lines.select do |line|
        parts = line.split(',')
        parts.size >= 2 && parts[1] == user_id
      end.reverse.map do |line|
        parts = line.split(',')
        if parts.size >= 5
          {
            "timestamp" => parts[0],
            "user_id" => parts[1],
            "substance_name" => parts[2].downcase.strip,
            "dosage" => parts[3],
            "route" => parts[4].downcase.strip,
            "source" => "sftp_csv_import"
          }
        else
          nil
        end
      end.compact
      
      if user_entries.empty?
        response.status_code = 404
        response.print({"error" => "No dose entries found for user #{user_id}"}.to_json)
        return
      end
      
      # Get the first n entries
      result_entries = user_entries.first(n)
      
      response.print({
        "data" => result_entries,
        "meta" => {
          "user_id" => user_id,
          "count" => result_entries.size,
          "requested" => n,
          "total_entries" => user_entries.size
        }
      }.to_json)
    rescue ex
      response.status_code = 500
      response.print({"error" => "Failed to fetch user dose list: #{ex.message}"}.to_json)
    end
  end

  private def handle_user_last_substance(response : HTTP::Server::Response, user_id : String, substance : String)
    begin
      csv_content = @sftp_client.fetch_csv_content
      lines = csv_content.split('\n')
      
      # Skip header and empty lines
      data_lines = lines[1..-1].reject(&.empty?)
      
      # Filter by user_id and substance, newest first
      substance_entries = data_lines.select do |line|
        parts = line.split(',')
        parts.size >= 3 && parts[1] == user_id && parts[2].downcase.strip == substance.downcase
      end.reverse.map do |line|
        parts = line.split(',')
        if parts.size >= 5
          {
            "timestamp" => parts[0],
            "user_id" => parts[1],
            "substance_name" => parts[2].downcase.strip,
            "dosage" => parts[3],
            "route" => parts[4].downcase.strip,
            "source" => "sftp_csv_import"
          }
        else
          nil
        end
      end.compact
      
      if substance_entries.empty?
        response.status_code = 404
        response.print({"error" => "No entries found for user #{user_id} and substance #{substance}"}.to_json)
        return
      end
      
      # Get the most recent entry
      last_entry = substance_entries.first
      
      response.print({
        "data" => last_entry,
        "meta" => {
          "user_id" => user_id,
          "substance" => substance,
          "total_entries_for_substance" => substance_entries.size
        }
      }.to_json)
    rescue ex
      response.status_code = 500
      response.print({"error" => "Failed to fetch user substance data: #{ex.message}"}.to_json)
    end
  end

  private def handle_user_howhighami(response : HTTP::Server::Response, user_id : String)
    begin
      csv_content = @sftp_client.fetch_csv_content
      lines = csv_content.split('\n')
      
      # Skip header and empty lines
      data_lines = lines[1..-1].reject(&.empty?)
      
      # Filter by user_id, newest first
      user_entries = data_lines.select do |line|
        parts = line.split(',')
        parts.size >= 3 && parts[1] == user_id
      end.reverse.map do |line|
        parts = line.split(',')
        if parts.size >= 5
          {
            "timestamp" => parts[0],
            "substance_name" => parts[2].downcase.strip,
            "dosage" => parts[3],
            "route" => parts[4].downcase.strip
          }
        else
          nil
        end
      end.compact
      
      if user_entries.empty?
        response.print({
          "active_substances" => [] of String,
          "meta" => {"message" => "No dose history found"}
        }.to_json)
        return
      end
      
      # Load duration and onset data from drugs.json
      duration_estimates = {} of String => Float64
      onset_estimates = {} of String => Float64  # In minutes
      begin
        drugs_json = File.read("data/drugs.json")
        drugs_data = JSON.parse(drugs_json)
        
        drugs_data.as_h.each do |drug_key, drug_info|
          name_json = drug_info["name"]?
          next unless name_json
          name = name_json.as_s
          
          # Process duration data
          if duration_info = drug_info["formatted_duration"]?
            duration_value_json = duration_info["value"]?
            duration_unit_json = duration_info["_unit"]?
            if duration_value_json && duration_unit_json
              duration_value = duration_value_json.as_s
              duration_unit = duration_unit_json.as_s
              
              if duration_unit == "hours"
                # Parse duration range like "3-5" or "8-12" - use upper bound
                if match = duration_value.match(/(\\d+(?:\\.\\d+)?)-(\\d+(?:\\.\\d+)?)/)
                  upper_bound = match[2].to_f
                  duration_estimates[name] = upper_bound
                elsif match = duration_value.match(/(\\d+(?:\\.\\d+)?)/)
                  single_value = match[1].to_f
                  duration_estimates[name] = single_value
                end
              end
            end
          end
          
          # Process onset data
          if onset_info = drug_info["formatted_onset"]?
            # Check for route-specific onsets (oral, insufflated, etc.)
            if onset_info.as_h?
              onset_info.as_h.each do |route, onset_data|
                route_key = "#{name}_#{route}"
                if onset_value_json = onset_data.as_h?["value"]?
                  onset_value = onset_value_json.as_s
                  onset_unit = onset_data.as_h?["_unit"]?.try(&.as_s) || "minutes"
                  
                  if onset_unit == "minutes"
                    if match = onset_value.match(/(\\d+(?:\\.\\d+)?)-(\\d+(?:\\.\\d+)?)/)
                      # Use average of range
                      onset_estimates[route_key] = (match[1].to_f + match[2].to_f) / 2
                    elsif match = onset_value.match(/(\\d+(?:\\.\\d+)?)/)
                      onset_estimates[route_key] = match[1].to_f
                    end
                  end
                end
              end
            elsif onset_value_json = onset_info["value"]?
              # Single onset value for all routes
              onset_value = onset_value_json.as_s
              onset_unit = onset_info["_unit"]?.try(&.as_s) || "minutes"
              
              if onset_unit == "minutes"
                if match = onset_value.match(/(\\d+(?:\\.\\d+)?)-(\\d+(?:\\.\\d+)?)/)
                  onset_estimates[name] = (match[1].to_f + match[2].to_f) / 2
                elsif match = onset_value.match(/(\\d+(?:\\.\\d+)?)/)
                  onset_estimates[name] = match[1].to_f
                end
              end
            end
          end
          
          # Add aliases for both duration and onset
          if aliases_json = drug_info["aliases"]?
            aliases_json.as_a.each do |alias_json|
              alias_name = alias_json.as_s
              if duration_estimates.has_key?(name)
                duration_estimates[alias_name] = duration_estimates[name]
              end
              if onset_estimates.has_key?(name)
                onset_estimates[alias_name] = onset_estimates[name]
              end
              # Copy route-specific onset data for aliases
              onset_estimates.each do |key, value|
                if key.starts_with?("#{name}_")
                  route = key.split("_", 2)[1]
                  onset_estimates["#{alias_name}_#{route}"] = value
                end
              end
            end
          end
        end
      rescue ex
        puts "Warning: Could not load drugs.json: #{ex.message}"
        duration_estimates = {} of String => Float64
        onset_estimates = {} of String => Float64
      end
      
      active_substances = [] of Hash(String, String | Float64 | Nil)
      current_time = Time.utc
      
      # Process all entries in chronological order to find 8-hour gaps
      recent_substances = [] of Hash(String, String)
      
      user_entries.each_with_index do |entry, index|
        begin
          dose_time = Time.parse_rfc3339(entry["timestamp"])
          hours_since = (current_time - dose_time).total_hours
          
          # Check if this is more than 8 hours after the previous entry
          if index > 0
            prev_entry = user_entries[index - 1]
            prev_time = Time.parse_rfc3339(prev_entry["timestamp"])
            gap_hours = (dose_time - prev_time).total_hours
            
            # If there's an 8+ hour gap, stop including earlier entries
            break if gap_hours >= 8.0
          end
          
          recent_substances << entry.transform_values(&.to_s)
          
        rescue Time::Format::Error
          # Skip entries with invalid timestamps
          next
        end
      end
      
      # Group by substance and get most recent for each from the recent list
      substances_last_use = {} of String => Hash(String, String)
      recent_substances.reverse.each do |entry|  # Reverse to get most recent first
        substance = entry["substance_name"]
        route = entry["route"]
        if !substances_last_use.has_key?(substance)
          substances_last_use[substance] = entry
        end
      end
      
      substances_last_use.each do |substance, entry|
        route = entry["route"]
        
        # Get duration from JSON data only
        duration_hours = duration_estimates[substance]?
        
        # Get onset data (try route-specific first, then general)
        route_key = "#{substance}_#{route}"
        onset_minutes = onset_estimates[route_key]? || onset_estimates[substance]?
        
        begin
          dose_time = Time.parse_rfc3339(entry["timestamp"])
          hours_since = (current_time - dose_time).total_hours
          minutes_since = hours_since * 60
          
          time_since_str = if hours_since < 1
            "#{(hours_since * 60).to_i} minutes ago"
          else
            "#{hours_since.round(1)} hours ago"
          end
          
          # Determine onset status
          onset_status = if onset_minutes
            if minutes_since < onset_minutes
              "pre-onset"
            elsif minutes_since < (onset_minutes + 30)  # 30min window for coming up
              "coming-up"
            else
              "active"
            end
          else
            nil  # No onset data available
          end
          
          substance_info = {
            "name" => substance,
            "route" => route,
            "dosage" => entry["dosage"],
            "last_dose_time" => dose_time.to_s("%Y-%m-%d %H:%M"),
            "time_since" => time_since_str,
            "onset_status" => onset_status
          } of String => String | Float64 | Nil
          
          # Add duration info if available
          if duration_hours
            substance_info["estimated_duration"] = "#{duration_hours} hours"
            hours_remaining = duration_hours - hours_since
            substance_info["hours_remaining"] = hours_remaining.round(1)
            substance_info["status"] = hours_remaining > 0 ? "active" : "worn-off"
          else
            substance_info["status"] = "unknown-duration"
          end
          
          # Add onset info if available
          if onset_minutes
            onset_str = if onset_minutes < 60
              "#{onset_minutes.to_i} min"
            else
              "#{(onset_minutes / 60).round(1)} hr"
            end
            substance_info["expected_onset"] = onset_str
          end
          
          active_substances << substance_info
          
        rescue Time::Format::Error
          # Skip entries with invalid timestamps
          next
        end
      end
      
      response.print({
        "active_substances" => active_substances,
        "meta" => {
          "total_logged_entries" => user_entries.size,
          "recent_entries_considered" => recent_substances.size,
          "unique_substances_in_recent" => substances_last_use.size,
          "current_time" => current_time.to_s("%Y-%m-%d %H:%M UTC"),
          "note" => "Includes recent substances until 8-hour gap"
        }
      }.to_json)
      
    rescue ex
      response.status_code = 500
      response.print({"error" => "Failed to fetch user howhighami data: #{ex.message}"}.to_json)
    end
  end
end