package api

import (
	"errors"
	"github.com/gin-gonic/gin"
	"github.com/rs/zerolog/log"
	"net"
	"net/http"
	"thuanle.me/ip-info/internal/data"
)

func HandleJson(c *gin.Context) {
	ip := c.ClientIP()
	// ClientIP can yield an unparseable value from malformed proxy headers;
	// reject it explicitly instead of letting Query fail into a 500.
	if net.ParseIP(ip) == nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid ip", "ip": ip})
		return
	}
	// selfLookup=true preserves the "default value when lookup internal ip"
	// contract (commit 9d07639): a valid but DB-absent client IP (loopback /
	// private addresses) returns 200 {"ip": ...} rather than a 404, since the
	// caller did not ask for a specific lookup.
	HandleIpInfo(c, ip, true)
}

func HandleOtherIp(c *gin.Context) {
	ip := c.Param("ip")
	if net.ParseIP(ip) == nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid ip", "ip": ip})
		return
	}
	// The caller explicitly requested this IP, so a not-found is a real 404.
	HandleIpInfo(c, ip, false)
}

func HandleIpInfo(c *gin.Context, ip string, selfLookup bool) {
	if !data.Ins().IsLoaded() {
		log.Error().Msg("DB is not loaded")
		c.JSON(http.StatusServiceUnavailable, gin.H{
			"error": "service unavailable, db still loading",
			"ip":    ip,
		})
		return
	}

	ipData, _, err := data.Ins().Query(ip)
	if err != nil {
		// Distinguish "not found" (valid IP, absent from DB) from other errors.
		if isNotFound(err) {
			if selfLookup {
				// Graceful default for the self-IP (/json) path: the client's
				// address is valid but simply absent from the public GeoIP DB.
				c.JSON(http.StatusOK, gin.H{"ip": ip})
				return
			}
			c.JSON(http.StatusNotFound, gin.H{"error": "not found", "ip": ip})
			return
		}
		log.Err(err).Msg("Failed to query IP")
		c.JSON(http.StatusInternalServerError, gin.H{"error": "internal error", "ip": ip})
		return
	}

	ipData["ip"] = ip
	c.JSON(http.StatusOK, ipData)
}

// isNotFound reports whether the query error means the IP was not present in
// the DB (as opposed to an invalid IP or an internal lookup failure).
func isNotFound(err error) bool {
	return errors.Is(err, data.ErrNotFound)
}
