package incidents

import (
	"encoding/json"
	"net/http"
	"time"

	"github.com/henrygd/beszel/internal/entities/incident"
	"github.com/pocketbase/dbx"
	"github.com/pocketbase/pocketbase/apis"
	"github.com/pocketbase/pocketbase/core"
)

// APIHandler handles incident API requests
type APIHandler struct {
	app core.App
}

// NewAPIHandler creates a new incidents API handler
func NewAPIHandler(app core.App) *APIHandler {
	return &APIHandler{app: app}
}

// RegisterRoutes registers incident API routes
func (h *APIHandler) RegisterRoutes(se *core.ServeEvent) {
	api := se.Router.Group("/api/beszel/incidents")
	api.Bind(apis.RequireAuth())

	api.GET("/", h.listIncidents)
	api.POST("/", h.createIncident)
	api.GET("/stats", h.getIncidentStats)
	api.GET("/calendar", h.getCalendarEvents)
	api.GET("/{id}", h.getIncident)
	api.PATCH("/{id}", h.updateIncident)
	api.POST("/{id}/acknowledge", h.acknowledgeIncident)
	api.POST("/{id}/resolve", h.resolveIncident)
	api.POST("/{id}/close", h.closeIncident)
	api.POST("/{id}/updates", h.addIncidentUpdate)
	api.GET("/{id}/updates", h.getIncidentUpdates)
}

// listIncidents lists all incidents for the authenticated user
func (h *APIHandler) listIncidents(e *core.RequestEvent) error {
	authRecord := e.Auth
	if authRecord == nil {
		return e.UnauthorizedError("unauthorized", nil)
	}

	// Get query params for filtering
	status := e.Request.URL.Query().Get("status")
	severity := e.Request.URL.Query().Get("severity")

	query := dbx.NewExp("user = {:user}", dbx.Params{"user": authRecord.Id})

	if status != "" {
		query = dbx.And(query, dbx.NewExp("status = {:status}", dbx.Params{"status": status}))
	}
	if severity != "" {
		query = dbx.And(query, dbx.NewExp("severity = {:severity}", dbx.Params{"severity": severity}))
	}

	records, err := h.app.FindAllRecords("incidents", query)
	if err != nil {
		return e.InternalServerError("failed to fetch incidents", err)
	}

	incidents := make([]map[string]interface{}, 0, len(records))
	for _, record := range records {
		incidents = append(incidents, h.recordToResponse(record))
	}

	return e.JSON(http.StatusOK, incidents)
}

// createIncident creates a new incident
func (h *APIHandler) createIncident(e *core.RequestEvent) error {
	authRecord := e.Auth
	if authRecord == nil {
		return e.UnauthorizedError("unauthorized", nil)
	}

	var req struct {
		Title       string  `json:"title"`
		Description string  `json:"description"`
		Type        string  `json:"type"`
		Severity    string  `json:"severity"`
		MonitorID   *string `json:"monitor,omitempty"`
		DomainID    *string `json:"domain,omitempty"`
		SystemID    *string `json:"system,omitempty"`
	}
	if err := json.NewDecoder(e.Request.Body).Decode(&req); err != nil {
		return e.BadRequestError("invalid request body", err)
	}

	if req.Title == "" || req.Type == "" {
		return e.BadRequestError("title and type are required", nil)
	}

	collection, err := h.app.FindCollectionByNameOrId("incidents")
	if err != nil {
		return e.InternalServerError("failed to find collection", err)
	}

	record := core.NewRecord(collection)
	record.Set("title", req.Title)
	record.Set("description", req.Description)
	record.Set("type", req.Type)
	record.Set("severity", req.Severity)
	record.Set("status", incident.StatusOpen)
	record.Set("started_at", time.Now())
	if req.MonitorID != nil {
		record.Set("monitor", *req.MonitorID)
	}
	if req.DomainID != nil {
		record.Set("domain", *req.DomainID)
	}
	if req.SystemID != nil {
		record.Set("system", *req.SystemID)
	}
	record.Set("user", authRecord.Id)

	if err := h.app.Save(record); err != nil {
		return e.InternalServerError("failed to create incident", err)
	}

	return e.JSON(http.StatusCreated, h.recordToResponse(record))
}

// getIncident gets a single incident
func (h *APIHandler) getIncident(e *core.RequestEvent) error {
	authRecord := e.Auth
	if authRecord == nil {
		return e.UnauthorizedError("unauthorized", nil)
	}

	id := e.Request.PathValue("id")
	record, err := h.app.FindRecordById("incidents", id)
	if err != nil {
		return e.NotFoundError("incident not found", err)
	}

	if record.GetString("user") != authRecord.Id {
		return e.ForbiddenError("not authorized", nil)
	}

	return e.JSON(http.StatusOK, h.recordToResponse(record))
}

// updateIncident updates an incident
func (h *APIHandler) updateIncident(e *core.RequestEvent) error {
	authRecord := e.Auth
	if authRecord == nil {
		return e.UnauthorizedError("unauthorized", nil)
	}

	id := e.Request.PathValue("id")
	record, err := h.app.FindRecordById("incidents", id)
	if err != nil {
		return e.NotFoundError("incident not found", err)
	}

	if record.GetString("user") != authRecord.Id {
		return e.ForbiddenError("not authorized", nil)
	}

	var req struct {
		Title       *string `json:"title,omitempty"`
		Description *string `json:"description,omitempty"`
		AssignedTo  *string `json:"assigned_to,omitempty"`
	}
	if err := json.NewDecoder(e.Request.Body).Decode(&req); err != nil {
		return e.BadRequestError("invalid request body", err)
	}

	if req.Title != nil {
		record.Set("title", *req.Title)
	}
	if req.Description != nil {
		record.Set("description", *req.Description)
	}
	if req.AssignedTo != nil {
		record.Set("assigned_to", *req.AssignedTo)
	}

	if err := h.app.Save(record); err != nil {
		return e.InternalServerError("failed to update incident", err)
	}

	return e.JSON(http.StatusOK, h.recordToResponse(record))
}

// acknowledgeIncident acknowledges an incident
func (h *APIHandler) acknowledgeIncident(e *core.RequestEvent) error {
	authRecord := e.Auth
	if authRecord == nil {
		return e.UnauthorizedError("unauthorized", nil)
	}

	id := e.Request.PathValue("id")
	record, err := h.app.FindRecordById("incidents", id)
	if err != nil {
		return e.NotFoundError("incident not found", err)
	}

	if record.GetString("user") != authRecord.Id {
		return e.ForbiddenError("not authorized", nil)
	}

	oldStatus := record.GetString("status")
	now := time.Now()
	record.Set("status", incident.StatusAcknowledged)
	record.Set("acknowledged_at", now)

	if err := h.app.Save(record); err != nil {
		return e.InternalServerError("failed to acknowledge incident", err)
	}

	// Add update record
	h.addUpdate(id, "Incident acknowledged", "status_change", &oldStatus, strPtr(incident.StatusAcknowledged), authRecord.Id)

	return e.JSON(http.StatusOK, h.recordToResponse(record))
}

// resolveIncident resolves an incident
func (h *APIHandler) resolveIncident(e *core.RequestEvent) error {
	authRecord := e.Auth
	if authRecord == nil {
		return e.UnauthorizedError("unauthorized", nil)
	}

	id := e.Request.PathValue("id")
	record, err := h.app.FindRecordById("incidents", id)
	if err != nil {
		return e.NotFoundError("incident not found", err)
	}

	if record.GetString("user") != authRecord.Id {
		return e.ForbiddenError("not authorized", nil)
	}

	var req struct {
		Resolution string `json:"resolution,omitempty"`
		RootCause  string `json:"root_cause,omitempty"`
	}
	if err := json.NewDecoder(e.Request.Body).Decode(&req); err != nil {
		return e.BadRequestError("invalid request body", err)
	}

	oldStatus := record.GetString("status")
	now := time.Now()
	record.Set("status", incident.StatusResolved)
	record.Set("resolved_at", now)
	if req.Resolution != "" {
		record.Set("resolution", req.Resolution)
	}
	if req.RootCause != "" {
		record.Set("root_cause", req.RootCause)
	}

	if err := h.app.Save(record); err != nil {
		return e.InternalServerError("failed to resolve incident", err)
	}

	// Add update record
	h.addUpdate(id, "Incident resolved: "+req.Resolution, "status_change", &oldStatus, strPtr(incident.StatusResolved), authRecord.Id)

	return e.JSON(http.StatusOK, h.recordToResponse(record))
}

// closeIncident closes an incident
func (h *APIHandler) closeIncident(e *core.RequestEvent) error {
	authRecord := e.Auth
	if authRecord == nil {
		return e.UnauthorizedError("unauthorized", nil)
	}

	id := e.Request.PathValue("id")
	record, err := h.app.FindRecordById("incidents", id)
	if err != nil {
		return e.NotFoundError("incident not found", err)
	}

	if record.GetString("user") != authRecord.Id {
		return e.ForbiddenError("not authorized", nil)
	}

	oldStatus := record.GetString("status")
	now := time.Now()
	record.Set("status", incident.StatusClosed)
	record.Set("closed_at", now)

	if err := h.app.Save(record); err != nil {
		return e.InternalServerError("failed to close incident", err)
	}

	// Add update record
	h.addUpdate(id, "Incident closed", "status_change", &oldStatus, strPtr(incident.StatusClosed), authRecord.Id)

	return e.JSON(http.StatusOK, h.recordToResponse(record))
}

// addIncidentUpdate adds an update to an incident
func (h *APIHandler) addIncidentUpdate(e *core.RequestEvent) error {
	authRecord := e.Auth
	if authRecord == nil {
		return e.UnauthorizedError("unauthorized", nil)
	}

	id := e.Request.PathValue("id")

	// Verify incident exists and belongs to user
	incident, err := h.app.FindRecordById("incidents", id)
	if err != nil {
		return e.NotFoundError("incident not found", err)
	}
	if incident.GetString("user") != authRecord.Id {
		return e.ForbiddenError("not authorized", nil)
	}

	var req struct {
		Message string `json:"message"`
	}
	if err := json.NewDecoder(e.Request.Body).Decode(&req); err != nil {
		return e.BadRequestError("invalid request body", err)
	}

	if req.Message == "" {
		return e.BadRequestError("message is required", nil)
	}

	h.addUpdate(id, req.Message, "note", nil, nil, authRecord.Id)

	return e.JSON(http.StatusCreated, map[string]string{"status": "added"})
}

// getIncidentUpdates gets all updates for an incident
func (h *APIHandler) getIncidentUpdates(e *core.RequestEvent) error {
	authRecord := e.Auth
	if authRecord == nil {
		return e.UnauthorizedError("unauthorized", nil)
	}

	id := e.Request.PathValue("id")

	// Verify incident exists and belongs to user
	incident, err := h.app.FindRecordById("incidents", id)
	if err != nil {
		return e.NotFoundError("incident not found", err)
	}
	if incident.GetString("user") != authRecord.Id {
		return e.ForbiddenError("not authorized", nil)
	}

	records, err := h.app.FindAllRecords("incident_updates",
		dbx.NewExp("incident = {:incident}", dbx.Params{"incident": id}),
	)
	if err != nil {
		return e.InternalServerError("failed to fetch updates", err)
	}

	updates := make([]map[string]interface{}, 0, len(records))
	for _, record := range records {
		updates = append(updates, map[string]interface{}{
			"id":          record.Id,
			"message":     record.GetString("message"),
			"update_type": record.GetString("update_type"),
			"old_status":  record.GetString("old_status"),
			"new_status":  record.GetString("new_status"),
			"created_by":  record.GetString("created_by"),
			"created_at":  record.GetDateTime("created_at").String(),
		})
	}

	return e.JSON(http.StatusOK, updates)
}

// getIncidentStats returns incident statistics
func (h *APIHandler) getIncidentStats(e *core.RequestEvent) error {
	authRecord := e.Auth
	if authRecord == nil {
		return e.UnauthorizedError("unauthorized", nil)
	}

	// Count by status
	total, _ := h.app.CountRecords("incidents", dbx.NewExp("user = {:user}", dbx.Params{"user": authRecord.Id}))
	open, _ := h.app.CountRecords("incidents", dbx.HashExp{"user": authRecord.Id, "status": incident.StatusOpen})
	acknowledged, _ := h.app.CountRecords("incidents", dbx.HashExp{"user": authRecord.Id, "status": incident.StatusAcknowledged})
	resolved, _ := h.app.CountRecords("incidents", dbx.HashExp{"user": authRecord.Id, "status": incident.StatusResolved})

	// Calculate MTTR
	resolvedRecords, _ := h.app.FindAllRecords("incidents",
		dbx.And(
			dbx.NewExp("user = {:user}", dbx.Params{"user": authRecord.Id}),
			dbx.NewExp("status = {:status}", dbx.Params{"status": incident.StatusResolved}),
		),
	)

	var totalResolutionTime time.Duration
	for _, r := range resolvedRecords {
		started := r.GetDateTime("started_at").Time()
		resolved := r.GetDateTime("resolved_at").Time()
		if !started.IsZero() && !resolved.IsZero() {
			totalResolutionTime += resolved.Sub(started)
		}
	}

	mttr := 0.0
	if len(resolvedRecords) > 0 {
		mttr = totalResolutionTime.Hours() / float64(len(resolvedRecords))
	}

	return e.JSON(http.StatusOK, map[string]interface{}{
		"total_incidents":        total,
		"open_incidents":         open,
		"acknowledged_incidents": acknowledged,
		"resolved_incidents":     resolved,
		"mttr_hours":             mttr,
	})
}

// getCalendarEvents returns events for calendar view
func (h *APIHandler) getCalendarEvents(e *core.RequestEvent) error {
	authRecord := e.Auth
	if authRecord == nil {
		return e.UnauthorizedError("unauthorized", nil)
	}

	events := []map[string]interface{}{}
	from, to := calendarRange(e)

	// Domain expirations
	domains, _ := h.app.FindAllRecords("domains",
		dbx.NewExp("user = {:user}", dbx.Params{"user": authRecord.Id}),
	)
	for _, d := range domains {
		expiryDate := d.GetDateTime("expiry_date").Time()
		if expiryDate.IsZero() || !dateInRange(expiryDate, from, to) {
			continue
		}
		domainName := d.GetString("domain_name")
		daysUntil := int(expiryDate.Sub(time.Now()).Hours() / 24)

		var color string
		if daysUntil <= 7 {
			color = "#ef4444" // red
		} else if daysUntil <= 30 {
			color = "#f59e0b" // orange
		} else {
			color = "#3b82f6" // blue
		}

		events = append(events, map[string]interface{}{
			"id":          "domain-" + d.Id,
			"title":       domainName + " expires",
			"date":        expiryDate.Format("2006-01-02"),
			"type":        "domain_expiry",
			"color":       color,
			"domain_id":   d.Id,
			"entity_id":   d.Id,
			"entity_name": domainName,
			"link":        "/domain/" + d.Id,
			"days_until":  daysUntil,
		})
	}

	// SSL expirations
	for _, d := range domains {
		sslExpiry := d.GetDateTime("ssl_valid_to").Time()
		if sslExpiry.IsZero() || !dateInRange(sslExpiry, from, to) {
			continue
		}
		domainName := d.GetString("domain_name")
		daysUntil := int(sslExpiry.Sub(time.Now()).Hours() / 24)

		var color string
		if daysUntil <= 7 {
			color = "#ef4444"
		} else if daysUntil <= 14 {
			color = "#f59e0b"
		} else {
			color = "#8b5cf6"
		}

		events = append(events, map[string]interface{}{
			"id":          "ssl-" + d.Id,
			"title":       domainName + " SSL expires",
			"date":        sslExpiry.Format("2006-01-02"),
			"type":        "ssl_expiry",
			"color":       color,
			"domain_id":   d.Id,
			"entity_id":   d.Id,
			"entity_name": domainName,
			"link":        "/domain/" + d.Id,
			"days_until":  daysUntil,
		})
	}

	// Incidents
	incidents, _ := h.app.FindAllRecords("incidents",
		dbx.NewExp("user = {:user}", dbx.Params{"user": authRecord.Id}),
	)
	for _, i := range incidents {
		startedAt := i.GetDateTime("started_at").Time()
		if startedAt.IsZero() || !dateInRange(startedAt, from, to) {
			continue
		}
		title := i.GetString("title")
		severity := i.GetString("severity")

		var color string
		switch severity {
		case incident.SeverityCritical:
			color = "#dc2626"
		case incident.SeverityHigh:
			color = "#ea580c"
		default:
			color = "#6b7280"
		}

		events = append(events, map[string]interface{}{
			"id":          "incident-" + i.Id,
			"title":       title,
			"date":        startedAt.Format("2006-01-02"),
			"type":        "incident",
			"color":       color,
			"incident_id": i.Id,
			"entity_id":   i.Id,
			"entity_name": title,
			"link":        "/incidents",
		})
	}

	return e.JSON(http.StatusOK, events)
}

func calendarRange(e *core.RequestEvent) (*time.Time, *time.Time) {
	var from, to *time.Time
	if value := e.Request.URL.Query().Get("from"); value != "" {
		if parsed, err := time.Parse("2006-01-02", value); err == nil {
			from = &parsed
		}
	}
	if value := e.Request.URL.Query().Get("to"); value != "" {
		if parsed, err := time.Parse("2006-01-02", value); err == nil {
			end := parsed.Add(24*time.Hour - time.Nanosecond)
			to = &end
		}
	}
	return from, to
}

func dateInRange(value time.Time, from, to *time.Time) bool {
	if from != nil && value.Before(*from) {
		return false
	}
	if to != nil && value.After(*to) {
		return false
	}
	return true
}

// addUpdate adds an update record
func (h *APIHandler) addUpdate(incidentID, message, updateType string, oldStatus, newStatus *string, createdBy string) {
	collection, err := h.app.FindCollectionByNameOrId("incident_updates")
	if err != nil {
		return
	}

	record := core.NewRecord(collection)
	record.Set("incident", incidentID)
	record.Set("message", message)
	record.Set("update_type", updateType)
	if oldStatus != nil {
		record.Set("old_status", *oldStatus)
	}
	if newStatus != nil {
		record.Set("new_status", *newStatus)
	}
	record.Set("created_by", createdBy)
	record.Set("created_at", time.Now())

	h.app.Save(record)
}

// recordToResponse converts a record to API response
func (h *APIHandler) recordToResponse(record *core.Record) map[string]interface{} {
	return map[string]interface{}{
		"id":              record.Id,
		"title":           record.GetString("title"),
		"description":     record.GetString("description"),
		"type":            record.GetString("type"),
		"severity":        record.GetString("severity"),
		"status":          record.GetString("status"),
		"monitor":         record.GetString("monitor"),
		"domain":          record.GetString("domain"),
		"system":          record.GetString("system"),
		"assigned_to":     record.GetString("assigned_to"),
		"started_at":      record.GetDateTime("started_at").String(),
		"acknowledged_at": record.GetDateTime("acknowledged_at").String(),
		"resolved_at":     record.GetDateTime("resolved_at").String(),
		"closed_at":       record.GetDateTime("closed_at").String(),
		"resolution":      record.GetString("resolution"),
		"root_cause":      record.GetString("root_cause"),
		"created":         record.GetDateTime("created").String(),
		"updated":         record.GetDateTime("updated").String(),
	}
}

func strPtr(s string) *string {
	return &s
}
