package api

import (
	"fmt"
	"log/slog"
	"net/http"
	"strings"

	"github.com/alexei/docker-watcher/internal/crypto"
	"github.com/alexei/docker-watcher/internal/dns"
	"github.com/alexei/docker-watcher/internal/store"
	"github.com/go-chi/chi/v5"
)

// dnsRecordView is the response format for DNS records with consumer context.
type dnsRecordView struct {
	FQDN         string `json:"fqdn"`
	Type         string `json:"type"`
	Content      string `json:"content"`
	ConsumerType string `json:"consumer_type"`
	ConsumerName string `json:"consumer_name"`
	ConsumerID   string `json:"consumer_id"`
	Status       string `json:"status"` // "synced", "orphaned", "missing"
}

// listDNSRecords handles GET /api/dns/records.
// In managed DNS mode: merges local dns_records with actual Cloudflare records to compute sync status.
// In wildcard mode: shows all expected FQDNs from active consumers (informational, no sync status).
func (s *Server) listDNSRecords(w http.ResponseWriter, r *http.Request) {
	settings, err := s.store.GetSettings()
	if err != nil {
		respondError(w, http.StatusInternalServerError, "failed to get settings: "+err.Error())
		return
	}

	consumerNames := s.buildConsumerNameMap()

	// In wildcard mode, show expected records from consumers without sync status.
	if settings.WildcardDNS {
		expectedFQDNs, err := s.computeExpectedFQDNs(settings)
		if err != nil {
			respondError(w, http.StatusInternalServerError, "failed to compute expected records: "+err.Error())
			return
		}
		var views []dnsRecordView
		for fqdn, consumer := range expectedFQDNs {
			parts := strings.SplitN(consumer, ":", 2)
			consumerType, consumerID := parts[0], ""
			if len(parts) > 1 {
				consumerID = parts[1]
			}
			name := consumerNames[consumer]
			if name == "" {
				name = consumerID
			}
			views = append(views, dnsRecordView{
				FQDN:         fqdn,
				Type:         "A",
				Content:      settings.ServerIP,
				ConsumerType: consumerType,
				ConsumerName: name,
				ConsumerID:   consumerID,
				Status:       "wildcard",
			})
		}
		if views == nil {
			views = []dnsRecordView{}
		}
		respondJSON(w, http.StatusOK, views)
		return
	}

	// Managed DNS mode: full sync status computation.

	// Get local tracked records.
	localRecords, err := s.store.ListDNSRecords()
	if err != nil {
		respondError(w, http.StatusInternalServerError, "failed to list local records: "+err.Error())
		return
	}

	// Try to get actual records from the DNS provider.
	var providerRecords []dns.Record
	provider := s.getOrCreateDNSProvider(settings)
	if provider != nil {
		providerRecords, err = provider.ListRecords(r.Context())
		if err != nil {
			slog.Warn("dns records: failed to list provider records", "error", err)
			// Continue with local-only view.
		}
	}

	// Build a map of provider records by FQDN.
	providerByFQDN := make(map[string]dns.Record, len(providerRecords))
	for _, rec := range providerRecords {
		providerByFQDN[rec.FQDN] = rec
	}

	// Build a set of local FQDNs.
	localFQDNs := make(map[string]bool, len(localRecords))
	for _, rec := range localRecords {
		localFQDNs[rec.FQDN] = true
	}

	var views []dnsRecordView

	// Process local records: check if they exist in provider.
	for _, local := range localRecords {
		status := "missing"
		content := settings.ServerIP
		if pRec, ok := providerByFQDN[local.FQDN]; ok {
			status = "synced"
			content = pRec.Content
		}

		name := consumerNames[local.ConsumerType+":"+local.ConsumerID]
		if name == "" {
			name = local.ConsumerID
		}

		views = append(views, dnsRecordView{
			FQDN:         local.FQDN,
			Type:         "A",
			Content:      content,
			ConsumerType: local.ConsumerType,
			ConsumerName: name,
			ConsumerID:   local.ConsumerID,
			Status:       status,
		})
	}

	// Find orphaned records: in provider but not in local tracking.
	for _, pRec := range providerRecords {
		if !localFQDNs[pRec.FQDN] {
			views = append(views, dnsRecordView{
				FQDN:         pRec.FQDN,
				Type:         pRec.Type,
				Content:      pRec.Content,
				ConsumerType: "",
				ConsumerName: "",
				ConsumerID:   "",
				Status:       "orphaned",
			})
		}
	}

	if views == nil {
		views = []dnsRecordView{}
	}

	respondJSON(w, http.StatusOK, views)
}

// deleteDNSRecord handles DELETE /api/dns/records/{fqdn}.
func (s *Server) deleteDNSRecord(w http.ResponseWriter, r *http.Request) {
	fqdn := chi.URLParam(r, "fqdn")
	if fqdn == "" {
		respondError(w, http.StatusBadRequest, "fqdn is required")
		return
	}

	settings, err := s.store.GetSettings()
	if err != nil {
		respondError(w, http.StatusInternalServerError, "failed to get settings: "+err.Error())
		return
	}

	provider := s.getOrCreateDNSProvider(settings)
	if provider != nil {
		if err := provider.DeleteRecord(r.Context(), fqdn); err != nil {
			respondError(w, http.StatusBadGateway, "failed to delete DNS record: "+err.Error())
			return
		}
	}

	// Remove local tracking.
	if err := s.store.DeleteDNSRecord(fqdn); err != nil {
		slog.Warn("delete dns tracking record", "fqdn", fqdn, "error", err)
	}

	respondJSON(w, http.StatusOK, map[string]string{"status": "deleted"})
}

// buildConsumerNameMap builds a lookup of "type:id" -> display name for DNS consumers.
func (s *Server) buildConsumerNameMap() map[string]string {
	names := make(map[string]string)

	// Instance consumers: "instance:id" -> "project/stage:tag"
	projects, _ := s.store.GetAllProjects()
	projectNames := make(map[string]string, len(projects))
	for _, p := range projects {
		projectNames[p.ID] = p.Name
	}

	for _, p := range projects {
		stages, _ := s.store.GetStagesByProjectID(p.ID)
		for _, st := range stages {
			instances, _ := s.store.GetInstancesByStageID(st.ID)
			for _, inst := range instances {
				names["instance:"+inst.ID] = p.Name + "/" + st.Name + ":" + inst.ImageTag
			}
		}
	}

	return names
}

// getOrCreateDNSProvider returns the server's DNS provider, or creates a temporary one from settings.
func (s *Server) getOrCreateDNSProvider(settings store.Settings) dns.Provider {
	if p := s.getDNSProviderLocked(); p != nil {
		return p
	}

	if settings.WildcardDNS || settings.DNSProvider == "" || settings.CloudflareAPIToken == "" {
		return nil
	}

	token, err := crypto.Decrypt(s.encKey, settings.CloudflareAPIToken)
	if err != nil {
		slog.Warn("dns: failed to decrypt token for provider creation", "error", err)
		return nil
	}

	provider, err := dns.NewProvider(settings.DNSProvider, dns.Config{
		Token:  token,
		ZoneID: settings.CloudflareZoneID,
	})
	if err != nil {
		slog.Warn("dns: failed to create provider", "error", err)
		return nil
	}
	return provider
}

// syncDNSRecords handles POST /api/dns/sync.
func (s *Server) syncDNSRecords(w http.ResponseWriter, r *http.Request) {
	settings, err := s.store.GetSettings()
	if err != nil {
		respondError(w, http.StatusInternalServerError, "failed to get settings: "+err.Error())
		return
	}

	if settings.WildcardDNS {
		respondError(w, http.StatusBadRequest, "DNS sync is disabled in wildcard mode")
		return
	}

	provider := s.getOrCreateDNSProvider(settings)
	if provider == nil {
		respondError(w, http.StatusBadRequest, "DNS provider not configured")
		return
	}

	// Compute expected FQDNs from active consumers.
	expectedFQDNs, err := s.computeExpectedFQDNs(settings)
	if err != nil {
		respondError(w, http.StatusInternalServerError, "failed to compute expected records: "+err.Error())
		return
	}

	// Get actual provider records.
	providerRecords, err := provider.ListRecords(r.Context())
	if err != nil {
		respondError(w, http.StatusBadGateway, "failed to list DNS records: "+err.Error())
		return
	}

	providerByFQDN := make(map[string]dns.Record, len(providerRecords))
	for _, rec := range providerRecords {
		providerByFQDN[rec.FQDN] = rec
	}

	// Get local tracking records.
	localRecords, err := s.store.ListDNSRecords()
	if err != nil {
		respondError(w, http.StatusInternalServerError, "failed to list local records: "+err.Error())
		return
	}
	localByFQDN := make(map[string]bool, len(localRecords))
	for _, rec := range localRecords {
		localByFQDN[rec.FQDN] = true
	}

	created := 0
	deleted := 0
	alreadySynced := 0

	// Create missing records.
	for fqdn, consumer := range expectedFQDNs {
		if _, exists := providerByFQDN[fqdn]; exists {
			alreadySynced++
			continue
		}

		recordID, err := provider.EnsureRecord(r.Context(), fqdn, settings.ServerIP)
		if err != nil {
			slog.Warn("dns sync: failed to create record", "fqdn", fqdn, "error", err)
			continue
		}

		// Track locally.
		parts := strings.SplitN(consumer, ":", 2)
		consumerType, consumerID := parts[0], ""
		if len(parts) > 1 {
			consumerID = parts[1]
		}
		if _, err := s.store.CreateDNSRecord(store.DNSRecord{
			FQDN:             fqdn,
			ProviderRecordID: recordID,
			ConsumerType:     consumerType,
			ConsumerID:       consumerID,
		}); err != nil {
			s.store.UpdateDNSRecordProviderID(fqdn, recordID)
		}
		created++
	}

	// Delete orphaned records (in provider + tracked locally, but no active consumer).
	for _, local := range localRecords {
		if _, expected := expectedFQDNs[local.FQDN]; !expected {
			if err := provider.DeleteRecord(r.Context(), local.FQDN); err != nil {
				slog.Warn("dns sync: failed to delete orphaned record", "fqdn", local.FQDN, "error", err)
				continue
			}
			s.store.DeleteDNSRecord(local.FQDN)
			deleted++
		}
	}

	respondJSON(w, http.StatusOK, map[string]int{
		"created":        created,
		"deleted":        deleted,
		"already_synced": alreadySynced,
	})
}

// computeExpectedFQDNs returns a map of FQDN -> "consumerType:consumerID" for all active DNS consumers.
func (s *Server) computeExpectedFQDNs(settings store.Settings) (map[string]string, error) {
	expected := make(map[string]string)

	// Instances with proxy enabled.
	projects, err := s.store.GetAllProjects()
	if err != nil {
		return nil, fmt.Errorf("get projects: %w", err)
	}
	for _, p := range projects {
		stages, err := s.store.GetStagesByProjectID(p.ID)
		if err != nil {
			slog.Warn("dns: failed to get stages", "project_id", p.ID, "error", err)
			continue
		}
		for _, st := range stages {
			if !st.EnableProxy {
				continue
			}
			instances, err := s.store.GetInstancesByStageID(st.ID)
			if err != nil {
				slog.Warn("dns: failed to get instances", "stage_id", st.ID, "error", err)
				continue
			}
			for _, inst := range instances {
				if inst.NpmProxyID > 0 && inst.Subdomain != "" && inst.Status == "running" {
					fqdn := inst.Subdomain + "." + settings.Domain
					expected[fqdn] = "instance:" + inst.ID
				}
			}
		}
	}

	return expected, nil
}
