package controllers

import (
	"context"
	"encoding/json"
	"strconv"
	"time"

	"doormile/db"
	"doormile/models"
	"doormile/utils"
)

// pincodeToState maps an Indian pincode to a state key using its first 3 digits.
// Covers the states Doormile operates in; other pincodes fall back to their 2-digit prefix.
func pincodeToState(pincode string) string {
	if len(pincode) < 3 {
		return pincode
	}
	p, err := strconv.Atoi(pincode[:3])
	if err != nil {
		if len(pincode) >= 2 {
			return pincode[:2]
		}
		return pincode
	}
	switch {
	case p >= 500 && p <= 535:
		return "AP_TS" // Andhra Pradesh / Telangana
	case p >= 560 && p <= 591:
		return "KA" // Karnataka
	case p >= 600 && p <= 643:
		return "TN" // Tamil Nadu
	case p >= 670 && p <= 695:
		return "KL" // Kerala
	case p >= 380 && p <= 396:
		return "GJ" // Gujarat
	case p >= 400 && p <= 444:
		return "MH" // Maharashtra
	default:
		if len(pincode) >= 2 {
			return pincode[:2]
		}
		return pincode
	}
}

// resolveZone determines the DoormilePricing zone from pickup and delivery pincodes.
//   - Local    — same 3-digit prefix (same city/sorting district)
//   - Regional — different city, same state
//   - National — different state
func resolveZone(pickupPincode, deliveryPincode string) string {
	if len(pickupPincode) >= 3 && len(deliveryPincode) >= 3 && pickupPincode[:3] == deliveryPincode[:3] {
		return "Local"
	}
	if pincodeToState(pickupPincode) == pincodeToState(deliveryPincode) {
		return "Regional"
	}
	return "National"
}

// pincodeToLatLon returns approximate coordinates for a pincode using its
// 3-digit postal division. Returns ok=false for unrecognised pincodes.
func pincodeToLatLon(pincode string) (lat, lon float64, ok bool) {
	if len(pincode) < 3 {
		return
	}
	p, err := strconv.Atoi(pincode[:3])
	if err != nil {
		return
	}
	switch {
	case p >= 500 && p <= 535:
		return 17.385044, 78.486671, true // Hyderabad / Telangana
	case p >= 560 && p <= 591:
		return 12.971599, 77.594566, true // Bengaluru / Karnataka
	case p >= 600 && p <= 643:
		return 13.082680, 80.270718, true // Chennai / Tamil Nadu
	case p >= 670 && p <= 695:
		return 10.850516, 76.271080, true // Kerala
	case p >= 380 && p <= 396:
		return 23.022505, 72.571365, true // Ahmedabad / Gujarat
	case p >= 400 && p <= 444:
		return 19.075984, 72.877656, true // Mumbai / Maharashtra
	}
	return
}

// normalizePricingCategory returns a DoormilePricing-compatible category string.
// Falls back to "General" for unknown or empty values.
func normalizePricingCategory(category string) string {
	switch category {
	case "General", "Documents", "Electronics", "Clothing", "Fragile", "Medical", "Automotive", "Food":
		return category
	default:
		return "General"
	}
}

// mapServiceTypeToPricing maps the booking service type to a DoormilePricing service type.
// DoormilePricing has only Normal and Express.
func mapServiceTypeToPricing(serviceType string) string {
	if serviceType == "Fast" || serviceType == "Superfast" {
		return "Express"
	}
	return "Normal"
}

// lookupDoormilePrice fetches pricing for the given zone/serviceType/weight/category
// from Redis first, falling back to Postgres on a cache miss.
// Returns (midpoint price, pricingID, true) on match, or (0, nil, false) when no rule matches.
func lookupDoormilePrice(zone, pricingServiceType string, weight float64, category string) (float64, *int, bool) {
	var allRules []models.DoormilePricing

	if db.Rdb != nil {
		ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
		cached, err := db.Rdb.Get(ctx, pricingCacheKey(zone, pricingServiceType)).Result()
		cancel()
		if err == nil {
			if jerr := json.Unmarshal([]byte(cached), &allRules); jerr != nil {
				utils.Warn("Corrupt pricing cache in booking, evicting", "zone", zone, "servicetype", pricingServiceType)
				invalidatePricingCache(zone, pricingServiceType)
				allRules = nil
			}
		}
	}

	if allRules == nil {
		var err error
		allRules, err = loadFromPostgres(zone, pricingServiceType)
		if err != nil || len(allRules) == 0 {
			return 0, nil, false
		}
	}

	matched := applyFilters(allRules, weight, category)
	if len(matched) == 0 && category != "General" {
		matched = applyFilters(allRules, weight, "General")
	}
	if len(matched) == 0 {
		return 0, nil, false
	}

	rule := matched[0]
	id := rule.Doormile_pricing_id
	return (rule.Minprice + rule.Maxprice) / 2, &id, true
}
