package handlers import ( "encoding/json" "net/http" "strconv" "strings" "time" "github.com/fiskerinc/cloud-services/pkg/logger" "github.com/fiskerinc/cloud-services/services/cost/services" ) // GetVinCost returns cost data for a specific VIN // GET /cost/vin/{vin}?from=2024-01-01&to=2024-01-31 func GetVinCost(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } // Extract VIN from path path := strings.TrimPrefix(r.URL.Path, "/cost/vin/") vin := strings.TrimSuffix(path, "/") if vin == "" { http.Error(w, "VIN required", http.StatusBadRequest) return } from, to := parseTimeRange(r) summary, err := services.GetVinCostSummary(vin, from, to) if err != nil { logger.Error().Err(err).Str("vin", vin).Msg("Failed to get VIN cost") http.Error(w, "Failed to get cost data", http.StatusInternalServerError) return } respondJSON(w, summary) } // GetFleetCost returns fleet-wide cost summary // GET /cost/fleet?from=2024-01-01&to=2024-01-31&limit=10 func GetFleetCost(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } from, to := parseTimeRange(r) limit := 10 if l := r.URL.Query().Get("limit"); l != "" { if parsed, err := strconv.Atoi(l); err == nil { limit = parsed } } summary, err := services.GetFleetCostSummary(from, to, limit) if err != nil { logger.Error().Err(err).Msg("Failed to get fleet cost") http.Error(w, "Failed to get cost data", http.StatusInternalServerError) return } respondJSON(w, summary) } // GetCostSummary returns a high-level cost summary // GET /cost/summary?period=day|week|month func GetCostSummary(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } period := r.URL.Query().Get("period") if period == "" { period = "day" } to := time.Now() var from time.Time switch period { case "week": from = to.AddDate(0, 0, -7) case "month": from = to.AddDate(0, -1, 0) default: from = to.AddDate(0, 0, -1) } summary, err := services.GetFleetCostSummary(from, to, 5) if err != nil { logger.Error().Err(err).Msg("Failed to get cost summary") http.Error(w, "Failed to get cost data", http.StatusInternalServerError) return } respondJSON(w, map[string]interface{}{ "period": period, "summary": summary, "cost_rates": map[string]interface{}{ "cloud": map[string]float64{ "cpu_per_core_hour": services.CloudCPUPerCoreHour, "memory_per_gb_hour": services.CloudMemoryPerGBHour, }, "onprem": map[string]float64{ "cpu_per_core_hour": services.OnpremCPUPerCoreHour, "memory_per_gb_hour": services.OnpremMemoryPerGBHour, }, }, }) } // GetCostComparison returns cloud vs on-prem cost comparison // GET /cost/comparison?from=2024-01-01&to=2024-01-31 func GetCostComparison(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } from, to := parseTimeRange(r) summary, err := services.GetFleetCostSummary(from, to, 0) if err != nil { logger.Error().Err(err).Msg("Failed to get cost comparison") http.Error(w, "Failed to get cost data", http.StatusInternalServerError) return } comparison := map[string]interface{}{ "period": map[string]interface{}{ "from": from, "to": to, }, "cloud": map[string]interface{}{ "total_cost_usd": summary.TotalCloudCost, "cost_per_vehicle": 0.0, "description": "Azure cloud hosting with managed services", }, "onprem": map[string]interface{}{ "total_cost_usd": summary.TotalOnpremCost, "cost_per_vehicle": 0.0, "description": "Self-hosted on owned hardware (amortized)", }, "savings": map[string]interface{}{ "total_usd": summary.TotalSavings, "percent": summary.SavingsPercent, "annual_projected": summary.TotalSavings * 12, }, "vehicle_count": summary.VehicleCount, } if summary.VehicleCount > 0 { comparison["cloud"].(map[string]interface{})["cost_per_vehicle"] = summary.TotalCloudCost / float64(summary.VehicleCount) comparison["onprem"].(map[string]interface{})["cost_per_vehicle"] = summary.TotalOnpremCost / float64(summary.VehicleCount) } respondJSON(w, comparison) } func parseTimeRange(r *http.Request) (from, to time.Time) { to = time.Now() from = to.AddDate(0, 0, -30) // Default to last 30 days if fromStr := r.URL.Query().Get("from"); fromStr != "" { if parsed, err := time.Parse("2006-01-02", fromStr); err == nil { from = parsed } } if toStr := r.URL.Query().Get("to"); toStr != "" { if parsed, err := time.Parse("2006-01-02", toStr); err == nil { to = parsed } } return } func respondJSON(w http.ResponseWriter, data interface{}) { w.Header().Set("Content-Type", "application/json") if err := json.NewEncoder(w).Encode(data); err != nil { logger.Error().Err(err).Msg("Failed to encode JSON response") } }