// Copyright 2026 Beacon Contributors
// SPDX-License-Identifier: AGPL-3.0-or-later

package config

import (
	"encoding/json"
	"fmt"

	"github.com/paulmach/orb"
	"github.com/paulmach/orb/geojson"
)

// ValidateBorder parses and validates raw as a GeoJSON Feature suitable for a region border
// map: well-formed Feature, geometry Polygon or MultiPolygon, every coordinate within
// lon [-180,180] / lat [-90,90], every ring closed (first point == last point, 4+ points --
// see orb.Ring.Closed). On success, returns the Feature re-marshaled to JSON with its bbox
// computed and set, so reads never need to recompute it (db/migrations/011_add_iata_border.sql).
//
// Coordinate order: GeoJSON packs coordinates [lon, lat], not [lat, lon]. A border authored
// with the axes swapped is still well-formed JSON and its numbers can still individually fall
// within the ranges checked here -- there is no way to detect a swapped axis order from the
// numbers alone. It lands in the wrong place on the map silently. Author borders as
// [lon, lat] and spot-check new ones visually; this function cannot catch that mistake.
func ValidateBorder(raw []byte) (json.RawMessage, error) {
	feat, err := geojson.UnmarshalFeature(raw)
	if err != nil {
		return nil, fmt.Errorf("not a well-formed GeoJSON Feature: %w", err)
	}
	if feat.Type != "Feature" {
		return nil, fmt.Errorf(`top-level "type" must be "Feature", got %q`, feat.Type)
	}
	switch geom := feat.Geometry.(type) {
	case orb.Polygon:
		if err := validateBorderPolygon(geom); err != nil {
			return nil, err
		}
	case orb.MultiPolygon:
		if len(geom) == 0 {
			return nil, fmt.Errorf("MultiPolygon has no polygons")
		}
		for i, poly := range geom {
			if err := validateBorderPolygon(poly); err != nil {
				return nil, fmt.Errorf("polygon %d: %w", i, err)
			}
		}
	default:
		return nil, fmt.Errorf("geometry must be Polygon or MultiPolygon, got %s", feat.Geometry.GeoJSONType())
	}
	// Computed server-side so reads (GET /iatas/{iata}/border) never need to walk every
	// vertex just to fit a map view -- see RFC 7946 section 5.
	feat.BBox = geojson.NewBBox(feat.Geometry.Bound())
	out, err := json.Marshal(feat)
	if err != nil {
		return nil, fmt.Errorf("re-marshaling validated border: %w", err)
	}
	return out, nil
}

func validateBorderPolygon(poly orb.Polygon) error {
	if len(poly) == 0 {
		return fmt.Errorf("polygon has no rings")
	}
	for i, ring := range poly {
		if !ring.Closed() {
			return fmt.Errorf("ring %d is not closed (needs 4+ points, first and last must match)", i)
		}
		for _, pt := range ring {
			lon, lat := pt.Lon(), pt.Lat()
			if lon < -180 || lon > 180 {
				return fmt.Errorf("ring %d: longitude %g out of range [-180,180]", i, lon)
			}
			if lat < -90 || lat > 90 {
				return fmt.Errorf("ring %d: latitude %g out of range [-90,90]", i, lat)
			}
		}
	}
	return nil
}
