package namespace

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"time"

	"github.com/DeBrosOfficial/network/pkg/gateway/ctxkeys"
	"github.com/DeBrosOfficial/network/pkg/ipfs"
	"github.com/DeBrosOfficial/network/pkg/rqlite"
	"go.uber.org/zap"
)

// NamespaceDeprovisioner is the interface for deprovisioning namespace clusters
type NamespaceDeprovisioner interface {
	DeprovisionCluster(ctx context.Context, namespaceID int64) error
}

// DeleteHandler handles namespace deletion requests
type DeleteHandler struct {
	deprovisioner NamespaceDeprovisioner
	ormClient     rqlite.Client
	ipfsClient    ipfs.IPFSClient // can be nil
	logger        *zap.Logger
}

// NewDeleteHandler creates a new delete handler
func NewDeleteHandler(dp NamespaceDeprovisioner, orm rqlite.Client, ipfsClient ipfs.IPFSClient, logger *zap.Logger) *DeleteHandler {
	return &DeleteHandler{
		deprovisioner: dp,
		ormClient:     orm,
		ipfsClient:    ipfsClient,
		logger:        logger.With(zap.String("component", "namespace-delete-handler")),
	}
}

// ServeHTTP handles DELETE /v1/namespace/delete
func (h *DeleteHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodDelete && r.Method != http.MethodPost {
		writeDeleteResponse(w, http.StatusMethodNotAllowed, map[string]interface{}{"error": "method not allowed"})
		return
	}

	// Get namespace from context (set by auth middleware — already ownership-verified)
	ns := ""
	if v := r.Context().Value(ctxkeys.NamespaceOverride); v != nil {
		if s, ok := v.(string); ok {
			ns = s
		}
	}
	if ns == "" || ns == "default" {
		writeDeleteResponse(w, http.StatusBadRequest, map[string]interface{}{"error": "cannot delete default namespace"})
		return
	}

	if h.deprovisioner == nil {
		writeDeleteResponse(w, http.StatusServiceUnavailable, map[string]interface{}{"error": "cluster provisioning not enabled"})
		return
	}

	// Resolve namespace ID
	var rows []map[string]interface{}
	if err := h.ormClient.Query(r.Context(), &rows, "SELECT id FROM namespaces WHERE name = ? LIMIT 1", ns); err != nil || len(rows) == 0 {
		writeDeleteResponse(w, http.StatusNotFound, map[string]interface{}{"error": "namespace not found"})
		return
	}

	var namespaceID int64
	switch v := rows[0]["id"].(type) {
	case float64:
		namespaceID = int64(v)
	case int64:
		namespaceID = v
	case int:
		namespaceID = int64(v)
	default:
		writeDeleteResponse(w, http.StatusInternalServerError, map[string]interface{}{"error": "invalid namespace ID type"})
		return
	}

	h.logger.Info("Deleting namespace",
		zap.String("namespace", ns),
		zap.Int64("namespace_id", namespaceID),
	)

	// 1. Deprovision the cluster (stops infra on ALL nodes, deletes cluster-state, deallocates ports, deletes DNS)
	if err := h.deprovisioner.DeprovisionCluster(r.Context(), namespaceID); err != nil {
		h.logger.Error("Failed to deprovision cluster", zap.Error(err))
		writeDeleteResponse(w, http.StatusInternalServerError, map[string]interface{}{"error": err.Error()})
		return
	}

	// 2. Clean up deployments (teardown replicas on all nodes, unpin IPFS, delete DB records)
	h.cleanupDeployments(r.Context(), ns)

	// 3. Unpin IPFS content from ipfs_content_ownership (separate from deployment CIDs)
	h.unpinNamespaceContent(r.Context(), ns)

	// 4. Clean up global tables that use namespace TEXT (not FK cascade)
	h.cleanupGlobalTables(r.Context(), ns)

	// 5. Delete API keys, ownership records, and namespace record
	h.ormClient.Exec(r.Context(), "DELETE FROM wallet_api_keys WHERE namespace_id = ?", namespaceID)
	h.ormClient.Exec(r.Context(), "DELETE FROM api_keys WHERE namespace_id = ?", namespaceID)
	h.ormClient.Exec(r.Context(), "DELETE FROM namespace_ownership WHERE namespace_id = ?", namespaceID)
	h.ormClient.Exec(r.Context(), "DELETE FROM namespaces WHERE id = ?", namespaceID)

	h.logger.Info("Namespace deleted successfully", zap.String("namespace", ns))

	writeDeleteResponse(w, http.StatusOK, map[string]interface{}{
		"status":    "deleted",
		"namespace": ns,
	})
}

// cleanupDeployments tears down all deployment replicas on all nodes, unpins IPFS content,
// and deletes all deployment-related DB records for the namespace.
// Best-effort: individual failures are logged but do not abort deletion.
func (h *DeleteHandler) cleanupDeployments(ctx context.Context, ns string) {
	type deploymentInfo struct {
		ID         string `db:"id"`
		Name       string `db:"name"`
		Type       string `db:"type"`
		ContentCID string `db:"content_cid"`
		BuildCID   string `db:"build_cid"`
	}
	var deps []deploymentInfo
	if err := h.ormClient.Query(ctx, &deps,
		"SELECT id, name, type, content_cid, build_cid FROM deployments WHERE namespace = ?", ns); err != nil {
		h.logger.Warn("Failed to query deployments for cleanup",
			zap.String("namespace", ns), zap.Error(err))
		return
	}

	if len(deps) == 0 {
		return
	}

	h.logger.Info("Cleaning up deployments for namespace",
		zap.String("namespace", ns),
		zap.Int("count", len(deps)))

	// 1. Send teardown to all replica nodes for each deployment
	for _, dep := range deps {
		h.teardownDeploymentReplicas(ctx, ns, dep.ID, dep.Name, dep.Type)
	}

	// 2. Unpin deployment IPFS content
	if h.ipfsClient != nil {
		for _, dep := range deps {
			if dep.ContentCID != "" {
				if err := h.ipfsClient.Unpin(ctx, dep.ContentCID); err != nil {
					h.logger.Warn("Failed to unpin deployment content CID",
						zap.String("deployment_id", dep.ID),
						zap.String("cid", dep.ContentCID), zap.Error(err))
				}
			}
			if dep.BuildCID != "" {
				if err := h.ipfsClient.Unpin(ctx, dep.BuildCID); err != nil {
					h.logger.Warn("Failed to unpin deployment build CID",
						zap.String("deployment_id", dep.ID),
						zap.String("cid", dep.BuildCID), zap.Error(err))
				}
			}
		}
	}

	// 3. Clean up deployment DB records (children first, since FK cascades disabled in rqlite)
	for _, dep := range deps {
		// Child tables with FK to deployments(id)
		h.ormClient.Exec(ctx, "DELETE FROM deployment_replicas WHERE deployment_id = ?", dep.ID)
		h.ormClient.Exec(ctx, "DELETE FROM port_allocations WHERE deployment_id = ?", dep.ID)
		h.ormClient.Exec(ctx, "DELETE FROM deployment_domains WHERE deployment_id = ?", dep.ID)
		h.ormClient.Exec(ctx, "DELETE FROM deployment_history WHERE deployment_id = ?", dep.ID)
		h.ormClient.Exec(ctx, "DELETE FROM deployment_env_vars WHERE deployment_id = ?", dep.ID)
		h.ormClient.Exec(ctx, "DELETE FROM deployment_events WHERE deployment_id = ?", dep.ID)
		h.ormClient.Exec(ctx, "DELETE FROM deployment_health_checks WHERE deployment_id = ?", dep.ID)
		// Tables with no FK constraint
		h.ormClient.Exec(ctx, "DELETE FROM dns_records WHERE deployment_id = ?", dep.ID)
		h.ormClient.Exec(ctx, "DELETE FROM global_deployment_subdomains WHERE deployment_id = ?", dep.ID)
	}
	h.ormClient.Exec(ctx, "DELETE FROM deployments WHERE namespace = ?", ns)

	h.logger.Info("Deployment cleanup completed",
		zap.String("namespace", ns),
		zap.Int("deployments_cleaned", len(deps)))
}

// teardownDeploymentReplicas sends a teardown request to every node that has a replica
// of the given deployment. Each node stops its process, removes files, and deallocates its port.
func (h *DeleteHandler) teardownDeploymentReplicas(ctx context.Context, ns, deploymentID, name, depType string) {
	type replicaNode struct {
		NodeID     string `db:"node_id"`
		InternalIP string `db:"internal_ip"`
	}
	var nodes []replicaNode
	query := `
		SELECT dr.node_id, COALESCE(dn.internal_ip, dn.ip_address) as internal_ip
		FROM deployment_replicas dr
		JOIN dns_nodes dn ON dr.node_id = dn.id
		WHERE dr.deployment_id = ?
	`
	if err := h.ormClient.Query(ctx, &nodes, query, deploymentID); err != nil {
		h.logger.Warn("Failed to query replica nodes for teardown",
			zap.String("deployment_id", deploymentID), zap.Error(err))
		return
	}

	if len(nodes) == 0 {
		return
	}

	payload := map[string]interface{}{
		"deployment_id": deploymentID,
		"namespace":     ns,
		"name":          name,
		"type":          depType,
	}
	jsonData, err := json.Marshal(payload)
	if err != nil {
		h.logger.Error("Failed to marshal teardown payload", zap.Error(err))
		return
	}

	for _, node := range nodes {
		url := fmt.Sprintf("http://%s:6001/v1/internal/deployments/replica/teardown", node.InternalIP)
		req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewReader(jsonData))
		if err != nil {
			h.logger.Warn("Failed to create teardown request",
				zap.String("node_id", node.NodeID), zap.Error(err))
			continue
		}
		req.Header.Set("Content-Type", "application/json")
		req.Header.Set("X-Orama-Internal-Auth", "replica-coordination")

		client := &http.Client{Timeout: 30 * time.Second}
		resp, err := client.Do(req)
		if err != nil {
			h.logger.Warn("Failed to send teardown to replica node",
				zap.String("deployment_id", deploymentID),
				zap.String("node_id", node.NodeID),
				zap.String("node_ip", node.InternalIP),
				zap.Error(err))
			continue
		}
		resp.Body.Close()
	}
}

// unpinNamespaceContent unpins all IPFS content owned by the namespace.
// Best-effort: individual failures are logged but do not abort deletion.
func (h *DeleteHandler) unpinNamespaceContent(ctx context.Context, ns string) {
	if h.ipfsClient == nil {
		h.logger.Debug("IPFS client not available, skipping IPFS cleanup")
		return
	}

	type cidRow struct {
		CID string `db:"cid"`
	}
	var rows []cidRow
	if err := h.ormClient.Query(ctx, &rows,
		"SELECT cid FROM ipfs_content_ownership WHERE namespace = ?", ns); err != nil {
		h.logger.Warn("Failed to query IPFS content for namespace",
			zap.String("namespace", ns), zap.Error(err))
		return
	}

	if len(rows) == 0 {
		return
	}

	h.logger.Info("Unpinning IPFS content for namespace",
		zap.String("namespace", ns),
		zap.Int("cid_count", len(rows)))

	for _, row := range rows {
		if err := h.ipfsClient.Unpin(ctx, row.CID); err != nil {
			h.logger.Warn("Failed to unpin CID (best-effort)",
				zap.String("cid", row.CID),
				zap.String("namespace", ns),
				zap.Error(err))
		}
	}
}

// cleanupGlobalTables deletes orphaned records from global tables that reference
// the namespace by TEXT name (not by integer FK, so CASCADE doesn't help).
// Best-effort: individual failures are logged but do not abort deletion.
func (h *DeleteHandler) cleanupGlobalTables(ctx context.Context, ns string) {
	tables := []struct {
		table  string
		column string
	}{
		{"global_deployment_subdomains", "namespace"},
		{"ipfs_content_ownership", "namespace"},
		{"functions", "namespace"},
		{"function_secrets", "namespace"},
		{"namespace_sqlite_databases", "namespace"},
		{"namespace_quotas", "namespace"},
		{"home_node_assignments", "namespace"},
		{"webrtc_rooms", "namespace_name"},
		{"namespace_webrtc_config", "namespace_name"},
	}

	for _, t := range tables {
		query := fmt.Sprintf("DELETE FROM %s WHERE %s = ?", t.table, t.column)
		if _, err := h.ormClient.Exec(ctx, query, ns); err != nil {
			h.logger.Warn("Failed to clean up global table (best-effort)",
				zap.String("table", t.table),
				zap.String("namespace", ns),
				zap.Error(err))
		}
	}
}

func writeDeleteResponse(w http.ResponseWriter, status int, resp map[string]interface{}) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(status)
	json.NewEncoder(w).Encode(resp)
}
