package qc

import (
	"fmt"
	"net/http"
	"verofy-backend/models"

	"github.com/gin-gonic/gin"
	"gorm.io/gorm"
)

type AerialEndpointResult struct {
	SegmentID       int                    `json:"segment_id"`
	SegmentName     string                 `json:"segment_name"`
	Type            string                 `json:"type"`
	IsValid         bool                   `json:"is_valid"`
	ErrorMessage    string                 `json:"error_message,omitempty"`
	StartPoleCount  int                    `json:"start_pole_count"`
	EndPoleCount    int                    `json:"end_pole_count"`
	StartPoleIDs    []int                  `json:"start_pole_ids,omitempty"`
	EndPoleIDs      []int                  `json:"end_pole_ids,omitempty"`
	Geometry        map[string]interface{} `json:"geometry,omitempty"`
}

type AerialEndpointSummary struct {
	TotalAerialSegments int                    `json:"total_aerial_segments"`
	ValidSegments       int                    `json:"valid_segments"`
	InvalidSegments     int                    `json:"invalid_segments"`
	PassRate            float64                `json:"pass_rate"`
	Results             []AerialEndpointResult `json:"results"`
}

func AerialEndpointsRoute(router *gin.Engine, db *gorm.DB, schema, segmentTable, mapIDCol, zoneCol, idCol, qcFlagCol string) {
	// Full aerial endpoints summary endpoint
	router.GET("/api/qc/aerial-endpoints", func(c *gin.Context) {
		mapID := c.Query("map_id")
		zone := c.Query("zone")
		if mapID == "" || zone == "" {
			c.JSON(http.StatusBadRequest, gin.H{"error": "map_id and zone are required"})
			return
		}

		summary, err := CheckAerialEndpoints(db, mapID, zone, schema, segmentTable, mapIDCol, zoneCol, idCol, qcFlagCol)
		if err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
			return
		}

		c.JSON(http.StatusOK, summary)
	})

	// Invalid segments only endpoint
	router.GET("/api/qc/aerial-endpoints/invalid", func(c *gin.Context) {
		mapID := c.Query("map_id")
		zone := c.Query("zone")
		if mapID == "" || zone == "" {
			c.JSON(http.StatusBadRequest, gin.H{"error": "map_id and zone are required"})
			return
		}

		invalid, err := GetInvalidAerialEndpoints(db, mapID, zone, schema, segmentTable, mapIDCol, zoneCol, idCol, qcFlagCol)
		if err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
			return
		}

		c.JSON(http.StatusOK, gin.H{
			"invalid_segments": invalid,
			"count":            len(invalid),
		})
	})

	// Update QC flags endpoint
	router.POST("/api/qc/aerial-endpoints/update-flags", func(c *gin.Context) {
		var request struct {
			SegmentIDs []int  `json:"segment_ids"`
			MapID      string `json:"map_id"`
			Zone       string `json:"zone"`
		}

		if err := c.ShouldBindJSON(&request); err != nil {
			c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
			return
		}

		err := UpdateAerialEndpointFlags(db, request.SegmentIDs, schema, segmentTable, idCol, qcFlagCol)
		if err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
			return
		}

		c.JSON(http.StatusOK, gin.H{"message": fmt.Sprintf("Updated QC flags for %d segments", len(request.SegmentIDs))})
	})
}

// CheckAerialEndpoints validates that aerial segments have exactly one pole at each endpoint
func CheckAerialEndpoints(db *gorm.DB, mapID, zone, schema, segmentTable, mapIDCol, zoneCol, idCol, qcFlagCol string) (*AerialEndpointSummary, error) {
	var segments []models.SegmentGeoJSON
	table := fmt.Sprintf("%s.%s", schema, segmentTable)

	// Query aerial segments
	err := db.Table(table).
		Select(fmt.Sprintf("id_0, %s, segment_type, segment_status, %s, protection_status, %s, ST_AsGeoJSON(ST_Transform(geom, 4326))::json AS geometry", mapIDCol, idCol, qcFlagCol)).
		Where(fmt.Sprintf("%s = ? AND %s = ? AND LOWER(segment_type) = ?", mapIDCol, zoneCol), mapID, zone, "aerial").
		Find(&segments).Error

	if err != nil {
		return nil, fmt.Errorf("failed to fetch aerial segments: %w", err)
	}

	// Get poles for the same map
	poles, err := getPoles(db, mapID, schema)
	if err != nil {
		return nil, fmt.Errorf("failed to fetch poles: %w", err)
	}

	summary := &AerialEndpointSummary{
		TotalAerialSegments: len(segments),
		Results:             make([]AerialEndpointResult, 0, len(segments)),
	}

	for _, segment := range segments {
		result := validateAerialEndpoints(segment, poles)
		summary.Results = append(summary.Results, result)

		if result.IsValid {
			summary.ValidSegments++
		} else {
			summary.InvalidSegments++
		}
	}

	// Calculate pass rate
	if summary.TotalAerialSegments > 0 {
		summary.PassRate = float64(summary.ValidSegments) / float64(summary.TotalAerialSegments) * 100
	}

	return summary, nil
}

// validateAerialEndpoints checks if aerial segment has exactly one pole at each endpoint
func validateAerialEndpoints(segment models.SegmentGeoJSON, poles []models.PolesGeoJSON) AerialEndpointResult {
	result := AerialEndpointResult{
		SegmentID: int(segment.ID),
		Type:      segment.SegmentType,
		IsValid:   false,
	}

	// Parse the geometry to get start and end coordinates
	if len(segment.Geometry) == 0 {
		result.ErrorMessage = "Segment has no geometry data"
		return result
	}

	startCoord, endCoord, geometry, err := getSegmentEndpoints(segment.Geometry)
	if err != nil {
		result.ErrorMessage = fmt.Sprintf("Failed to parse geometry: %v", err)
		return result
	}

	result.Geometry = geometry

	// Check for poles near start and end coordinates
	// Using a buffer distance of ~10 meters (0.0001 degrees approximately)
	bufferDistance := 0.0001

	startPoles, startPoleIDs := getPolesNearCoordinate(startCoord, poles, bufferDistance)
	endPoles, endPoleIDs := getPolesNearCoordinate(endCoord, poles, bufferDistance)

	result.StartPoleCount = startPoles
	result.EndPoleCount = endPoles
	result.StartPoleIDs = startPoleIDs
	result.EndPoleIDs = endPoleIDs

	// Valid if exactly ONE pole at each endpoint
	if startPoles == 1 && endPoles == 1 {
		result.IsValid = true
	} else {
		errorParts := []string{}
		if startPoles == 0 {
			errorParts = append(errorParts, "no pole at start")
		} else if startPoles > 1 {
			errorParts = append(errorParts, fmt.Sprintf("%d poles at start (should be 1)", startPoles))
		}
		if endPoles == 0 {
			errorParts = append(errorParts, "no pole at end")
		} else if endPoles > 1 {
			errorParts = append(errorParts, fmt.Sprintf("%d poles at end (should be 1)", endPoles))
		}

		if len(errorParts) > 0 {
			result.ErrorMessage = fmt.Sprintf("Aerial segment pole issues: %v", errorParts)
		}
	}

	return result
}

// getPolesNearCoordinate counts poles within buffer distance of coordinate and returns their IDs
func getPolesNearCoordinate(coord [2]float64, poles []models.PolesGeoJSON, buffer float64) (int, []int) {
	count := 0
	poleIDs := []int{}

	for _, pole := range poles {
		if poleCoord, err := getPointCoordinates(pole.Geometry); err == nil {
			if distance(coord, poleCoord) <= buffer {
				count++
				if pole.ID != nil {
					poleIDs = append(poleIDs, *pole.ID)
				}
			}
		}
	}

	return count, poleIDs
}

// GetInvalidAerialEndpoints returns only the segments that failed the check
func GetInvalidAerialEndpoints(db *gorm.DB, mapID, zone, schema, segmentTable, mapIDCol, zoneCol, idCol, qcFlagCol string) ([]AerialEndpointResult, error) {
	summary, err := CheckAerialEndpoints(db, mapID, zone, schema, segmentTable, mapIDCol, zoneCol, idCol, qcFlagCol)
	if err != nil {
		return nil, err
	}

	var invalid []AerialEndpointResult
	for _, result := range summary.Results {
		if !result.IsValid {
			invalid = append(invalid, result)
		}
	}

	return invalid, nil
}

// UpdateAerialEndpointFlags updates QC flags for invalid segments
func UpdateAerialEndpointFlags(db *gorm.DB, segmentIDs []int, schema, segmentTable, idCol, qcFlagCol string) error {
	if len(segmentIDs) == 0 {
		return nil
	}

	table := fmt.Sprintf("%s.%s", schema, segmentTable)

	return db.Table(table).
		Where(fmt.Sprintf("%s IN ?", idCol), segmentIDs).
		Update(qcFlagCol, "aerial_endpoint_issue").Error
}
