package api

import (
	"context"
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"strconv"
	"strings"
	"time"

	"gitea.dooplex.hu/admin/felhom-controller/internal/backup"
	"gitea.dooplex.hu/admin/felhom-controller/internal/config"
	"gitea.dooplex.hu/admin/felhom-controller/internal/metrics"
	"gitea.dooplex.hu/admin/felhom-controller/internal/stacks"
	catalogsync "gitea.dooplex.hu/admin/felhom-controller/internal/sync"
	"gitea.dooplex.hu/admin/felhom-controller/internal/system"
)

// Router handles all /api/* requests.
type Router struct {
	cfg          *config.Config
	stackMgr     *stacks.Manager
	syncer       *catalogsync.Syncer
	cpuCollector *system.CPUCollector
	backupMgr    *backup.Manager
	metricsStore *metrics.MetricsStore
	logger       *log.Logger
}

func NewRouter(cfg *config.Config, stackMgr *stacks.Manager, syncer *catalogsync.Syncer, cpuCollector *system.CPUCollector, backupMgr *backup.Manager, metricsStore *metrics.MetricsStore, logger *log.Logger) *Router {
	return &Router{cfg: cfg, stackMgr: stackMgr, syncer: syncer, cpuCollector: cpuCollector, backupMgr: backupMgr, metricsStore: metricsStore, logger: logger}
}

type apiResponse struct {
	OK      bool        `json:"ok"`
	Data    interface{} `json:"data,omitempty"`
	Error   string      `json:"error,omitempty"`
	Message string      `json:"message,omitempty"`
}

// ServeHTTP routes /api/* requests.
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	path := strings.TrimPrefix(req.URL.Path, "/api")
	path = strings.TrimSuffix(path, "/")

	switch {
	// GET /api/stacks
	case path == "/stacks" && req.Method == http.MethodGet:
		r.listStacks(w, req)

	// POST /api/stacks/rescan — re-scan stacks directory for new/removed stacks
	case path == "/stacks/rescan" && req.Method == http.MethodPost:
		r.rescanStacks(w, req)

	// GET /api/stacks/{name}
	case strings.HasPrefix(path, "/stacks/") && req.Method == http.MethodGet && !hasSubpath(path, "/stacks/"):
		r.getStack(w, req, trimSegment(path, "/stacks/"))

	// GET /api/stacks/{name}/deploy-fields
	case hasSuffix(path, "/deploy-fields") && req.Method == http.MethodGet:
		r.getDeployFields(w, req, extractName(path, "/deploy-fields"))

	// POST /api/stacks/{name}/deploy
	case hasSuffix(path, "/deploy") && req.Method == http.MethodPost:
		r.deployStack(w, req, extractName(path, "/deploy"))

	// POST /api/stacks/{name}/start
	case hasSuffix(path, "/start") && req.Method == http.MethodPost:
		r.actionStack(w, "start", extractName(path, "/start"))

	// POST /api/stacks/{name}/stop
	case hasSuffix(path, "/stop") && req.Method == http.MethodPost:
		r.actionStack(w, "stop", extractName(path, "/stop"))

	// POST /api/stacks/{name}/restart
	case hasSuffix(path, "/restart") && req.Method == http.MethodPost:
		r.actionStack(w, "restart", extractName(path, "/restart"))

	// POST /api/stacks/{name}/update
	case hasSuffix(path, "/update") && req.Method == http.MethodPost:
		r.actionStack(w, "update", extractName(path, "/update"))

	// POST /api/stacks/{name}/optional-config
	case hasSuffix(path, "/optional-config") && req.Method == http.MethodPost:
		r.updateOptionalConfig(w, req, extractName(path, "/optional-config"))

	// GET /api/stacks/{name}/logs
	case hasSuffix(path, "/logs") && req.Method == http.MethodGet:
		r.getStackLogs(w, req, extractName(path, "/logs"))

	// GET /api/stacks/{name}/hdd-data
	case hasSuffix(path, "/hdd-data") && req.Method == http.MethodGet:
		r.getStackHDDData(w, req, extractName(path, "/hdd-data"))

	// DELETE /api/stacks/{name}
	case strings.HasPrefix(path, "/stacks/") && req.Method == http.MethodDelete && !hasSubpath(path, "/stacks/"):
		r.deleteStack(w, req, trimSegment(path, "/stacks/"))

	// POST /api/sync — trigger immediate catalog sync
	case path == "/sync" && req.Method == http.MethodPost:
		r.triggerSync(w, req)

	// GET /api/system/info
	case path == "/system/info" && req.Method == http.MethodGet:
		r.systemInfo(w, req)

	// GET /api/backup/status
	case path == "/backup/status" && req.Method == http.MethodGet:
		r.backupStatus(w, req)

	// POST /api/backup/run
	case path == "/backup/run" && req.Method == http.MethodPost:
		r.triggerBackup(w, req)

	// GET /api/metrics/system
	case path == "/metrics/system" && req.Method == http.MethodGet:
		r.metricsSystem(w, req)

	// GET /api/metrics/containers/summary
	case path == "/metrics/containers/summary" && req.Method == http.MethodGet:
		r.metricsContainerSummary(w, req)

	// GET /api/metrics/containers/{name}
	case strings.HasPrefix(path, "/metrics/containers/") && req.Method == http.MethodGet:
		name := strings.TrimPrefix(path, "/metrics/containers/")
		r.metricsContainer(w, req, name)

	// GET /api/metrics/sysinfo
	case path == "/metrics/sysinfo" && req.Method == http.MethodGet:
		r.metricsSysInfo(w, req)

	default:
		writeJSON(w, http.StatusNotFound, apiResponse{OK: false, Error: "endpoint not found"})
	}
}

// HealthHandler responds to /api/health (no auth required).
func (r *Router) HealthHandler(w http.ResponseWriter, req *http.Request) {
	writeJSON(w, http.StatusOK, apiResponse{OK: true, Message: "felhom-controller is healthy"})
}

// --- Stack handlers ---

func (r *Router) listStacks(w http.ResponseWriter, _ *http.Request) {
	writeJSON(w, http.StatusOK, apiResponse{OK: true, Data: r.stackMgr.GetStacks()})
}

func (r *Router) rescanStacks(w http.ResponseWriter, _ *http.Request) {
	r.logger.Printf("[API] Manual stack rescan requested")
	if err := r.stackMgr.ScanStacks(); err != nil {
		r.logger.Printf("[API] Stack rescan failed: %v", err)
		writeJSON(w, http.StatusInternalServerError, apiResponse{OK: false, Error: err.Error()})
		return
	}
	stackCount := len(r.stackMgr.GetStacks())
	r.logger.Printf("[API] Stack rescan completed: %d stacks found", stackCount)
	writeJSON(w, http.StatusOK, apiResponse{
		OK:      true,
		Message: fmt.Sprintf("Rescan completed: %d stacks found", stackCount),
	})
}

func (r *Router) getStack(w http.ResponseWriter, _ *http.Request, name string) {
	stack, ok := r.stackMgr.GetStack(name)
	if !ok {
		writeJSON(w, http.StatusNotFound, apiResponse{OK: false, Error: "stack not found: " + name})
		return
	}
	writeJSON(w, http.StatusOK, apiResponse{OK: true, Data: stack})
}

func (r *Router) getDeployFields(w http.ResponseWriter, _ *http.Request, name string) {
	meta, appCfg, err := r.stackMgr.GetDeployFields(name)
	if err != nil {
		writeJSON(w, http.StatusNotFound, apiResponse{OK: false, Error: err.Error()})
		return
	}

	data := map[string]interface{}{
		"metadata":   meta,
		"app_config": appCfg,
		"domain":     r.cfg.Customer.Domain,
		"logo_url":   r.cfg.AppLogoURL(meta.Slug),
	}
	writeJSON(w, http.StatusOK, apiResponse{OK: true, Data: data})
}

func (r *Router) deployStack(w http.ResponseWriter, req *http.Request, name string) {
	r.logger.Printf("[API] Deploy requested for stack: %s", name)

	var body struct {
		Values map[string]string `json:"values"`
	}
	if err := json.NewDecoder(req.Body).Decode(&body); err != nil {
		writeJSON(w, http.StatusBadRequest, apiResponse{OK: false, Error: "invalid request body"})
		return
	}

	deployReq := stacks.DeployRequest{
		StackName: name,
		Values:    body.Values,
	}

	warning, err := r.stackMgr.DeployStack(deployReq)
	if err != nil {
		r.logger.Printf("[API] Deploy failed for %s: %v", name, err)
		status := http.StatusInternalServerError
		if strings.Contains(err.Error(), "already deployed") {
			status = http.StatusConflict
		}
		if strings.Contains(err.Error(), "required field") || strings.Contains(err.Error(), "does not exist") || strings.Contains(err.Error(), "kötelező") || strings.Contains(err.Error(), "memória") {
			status = http.StatusBadRequest
		}
		writeJSON(w, status, apiResponse{OK: false, Error: err.Error()})
		return
	}

	resp := apiResponse{OK: true, Message: "Stack " + name + " deployed"}
	if warning != "" {
		resp.Data = map[string]string{"warning": warning}
	}
	writeJSON(w, http.StatusOK, resp)
}

func (r *Router) actionStack(w http.ResponseWriter, action, name string) {
	r.logger.Printf("[API] %s requested for stack: %s", action, name)

	// Protected stacks only allow restart — block all other actions
	if r.cfg.IsProtectedStack(name) && action != "restart" {
		writeJSON(w, http.StatusForbidden, apiResponse{OK: false, Error: fmt.Sprintf("cannot %s protected stack %s", action, name)})
		return
	}

	var err error
	switch action {
	case "start":
		err = r.stackMgr.StartStack(name)
	case "stop":
		err = r.stackMgr.StopStack(name)
	case "restart":
		err = r.stackMgr.RestartStack(name)
	case "update":
		err = r.stackMgr.UpdateStack(name)
	}

	if err != nil {
		status := http.StatusInternalServerError
		if strings.Contains(err.Error(), "protected") {
			status = http.StatusForbidden
		}
		if strings.Contains(err.Error(), "not found") {
			status = http.StatusNotFound
		}
		writeJSON(w, status, apiResponse{OK: false, Error: err.Error()})
		return
	}

	writeJSON(w, http.StatusOK, apiResponse{OK: true, Message: "Stack " + name + " " + action + " completed"})
}

func (r *Router) updateOptionalConfig(w http.ResponseWriter, req *http.Request, name string) {
	r.logger.Printf("[API] Optional config update requested for stack: %s", name)

	var body struct {
		Values map[string]string `json:"values"`
	}
	if err := json.NewDecoder(req.Body).Decode(&body); err != nil {
		writeJSON(w, http.StatusBadRequest, apiResponse{OK: false, Error: "invalid request body"})
		return
	}

	if err := r.stackMgr.UpdateOptionalConfig(name, body.Values); err != nil {
		r.logger.Printf("[API] Optional config update failed for %s: %v", name, err)
		writeJSON(w, http.StatusInternalServerError, apiResponse{OK: false, Error: err.Error()})
		return
	}

	writeJSON(w, http.StatusOK, apiResponse{OK: true, Message: "Beállítások frissítve"})
}

func (r *Router) getStackLogs(w http.ResponseWriter, req *http.Request, name string) {
	lines := 100
	if v := req.URL.Query().Get("lines"); v != "" {
		if n, err := strconv.Atoi(v); err == nil && n > 0 {
			lines = n
		}
	}

	output, err := r.stackMgr.GetLogs(name, lines)
	if err != nil {
		writeJSON(w, http.StatusInternalServerError, apiResponse{OK: false, Error: err.Error()})
		return
	}
	writeJSON(w, http.StatusOK, apiResponse{OK: true, Data: map[string]string{"logs": output}})
}

func (r *Router) getStackHDDData(w http.ResponseWriter, _ *http.Request, name string) {
	resp, err := r.stackMgr.GetStackHDDData(name)
	if err != nil {
		writeJSON(w, http.StatusNotFound, apiResponse{OK: false, Error: err.Error()})
		return
	}
	writeJSON(w, http.StatusOK, apiResponse{OK: true, Data: resp})
}

func (r *Router) deleteStack(w http.ResponseWriter, req *http.Request, name string) {
	r.logger.Printf("[API] Delete requested for stack: %s", name)

	var body struct {
		RemoveHDDData bool `json:"remove_hdd_data"`
	}
	if err := json.NewDecoder(req.Body).Decode(&body); err != nil {
		body.RemoveHDDData = false
	}

	resp, err := r.stackMgr.DeleteStack(name, body.RemoveHDDData)
	if err != nil {
		r.logger.Printf("[API] Delete failed for %s: %v", name, err)
		status := http.StatusInternalServerError
		if strings.Contains(err.Error(), "protected") {
			status = http.StatusForbidden
		}
		if strings.Contains(err.Error(), "not found") {
			status = http.StatusNotFound
		}
		if strings.Contains(err.Error(), "not orphaned") || strings.Contains(err.Error(), "still running") {
			status = http.StatusConflict
		}
		writeJSON(w, status, apiResponse{OK: false, Error: err.Error()})
		return
	}

	writeJSON(w, http.StatusOK, apiResponse{OK: true, Data: resp, Message: "Stack " + name + " deleted"})
}

func (r *Router) triggerSync(w http.ResponseWriter, _ *http.Request) {
	r.logger.Println("[API] Manual catalog sync requested")
	result := r.syncer.TriggerSync()
	if !result.OK {
		writeJSON(w, http.StatusTooManyRequests, apiResponse{OK: false, Error: result.Message})
		return
	}
	r.logger.Printf("[API] Catalog sync completed: %s", result.Message)
	writeJSON(w, http.StatusOK, apiResponse{OK: true, Message: result.Message, Data: result})
}

func (r *Router) systemInfo(w http.ResponseWriter, _ *http.Request) {
	info := system.GetInfo(r.cfg.Paths.HDDPath, r.cpuCollector)
	syncStatus := r.syncer.Status()
	data := map[string]interface{}{
		"system":      info,
		"sync_status": syncStatus,
	}
	writeJSON(w, http.StatusOK, apiResponse{OK: true, Data: data})
}

// --- Backup handlers ---

func (r *Router) backupStatus(w http.ResponseWriter, _ *http.Request) {
	if r.backupMgr == nil {
		writeJSON(w, http.StatusOK, apiResponse{OK: true, Data: map[string]interface{}{
			"enabled": false,
		}})
		return
	}

	dbDump, backupSt := r.backupMgr.GetStatus()
	data := map[string]interface{}{
		"enabled": true,
		"running": r.backupMgr.IsRunning(),
	}

	if dbDump != nil {
		data["db_dump"] = map[string]interface{}{
			"last_run": dbDump.LastRun,
			"success":  dbDump.Success,
			"duration": dbDump.Duration.String(),
			"count":    len(dbDump.Results),
		}
	}

	if backupSt != nil {
		backupData := map[string]interface{}{
			"last_run": backupSt.LastRun,
			"success":  backupSt.Success,
			"duration": backupSt.Duration.String(),
		}
		if backupSt.Snapshot != nil {
			backupData["snapshot_id"] = backupSt.Snapshot.SnapshotID
			backupData["files_new"] = backupSt.Snapshot.FilesNew
			backupData["data_added"] = backupSt.Snapshot.DataAdded
		}
		if backupSt.RepoStats != nil {
			backupData["repo_size"] = backupSt.RepoStats.TotalSize
			backupData["snapshot_count"] = backupSt.RepoStats.SnapshotCount
		}
		data["backup"] = backupData
	}

	writeJSON(w, http.StatusOK, apiResponse{OK: true, Data: data})
}

func (r *Router) triggerBackup(w http.ResponseWriter, _ *http.Request) {
	if r.backupMgr == nil {
		writeJSON(w, http.StatusBadRequest, apiResponse{OK: false, Error: "Backup not configured"})
		return
	}

	if r.backupMgr.IsRunning() {
		writeJSON(w, http.StatusConflict, apiResponse{OK: false, Error: "Mentés már folyamatban"})
		return
	}

	r.logger.Println("[API] Manual backup triggered")
	go r.backupMgr.RunFullBackup(context.Background())

	writeJSON(w, http.StatusOK, apiResponse{OK: true, Message: "Mentés elindítva"})
}

// --- Metrics handlers ---

func (r *Router) metricsSystem(w http.ResponseWriter, req *http.Request) {
	if r.metricsStore == nil {
		writeJSON(w, http.StatusOK, apiResponse{OK: true, Data: map[string]interface{}{"labels": []int{}, "cpu": []float64{}, "memory": []float64{}, "temp": []float64{}, "load1": []float64{}}})
		return
	}

	from, to := parseTimeRange(req)
	resolution := parseResolution(req, 200)

	samples, err := r.metricsStore.QuerySystemMetrics(from, to, resolution)
	if err != nil {
		writeJSON(w, http.StatusInternalServerError, apiResponse{OK: false, Error: err.Error()})
		return
	}

	// Flatten into arrays for Chart.js
	labels := make([]int64, len(samples))
	cpu := make([]float64, len(samples))
	memory := make([]float64, len(samples))
	temp := make([]float64, len(samples))
	load1 := make([]float64, len(samples))

	for i, s := range samples {
		labels[i] = s.Timestamp
		cpu[i] = s.CPUPercent
		memory[i] = float64(s.MemUsedMB) / 1024.0 // Convert to GB
		temp[i] = s.TempCelsius
		load1[i] = s.LoadAvg1
	}

	writeJSON(w, http.StatusOK, apiResponse{OK: true, Data: map[string]interface{}{
		"labels": labels,
		"cpu":    cpu,
		"memory": memory,
		"temp":   temp,
		"load1":  load1,
	}})
}

func (r *Router) metricsContainerSummary(w http.ResponseWriter, _ *http.Request) {
	if r.metricsStore == nil {
		writeJSON(w, http.StatusOK, apiResponse{OK: true, Data: []interface{}{}})
		return
	}

	summary, err := r.metricsStore.QueryContainerSummary()
	if err != nil {
		writeJSON(w, http.StatusInternalServerError, apiResponse{OK: false, Error: err.Error()})
		return
	}

	writeJSON(w, http.StatusOK, apiResponse{OK: true, Data: summary})
}

func (r *Router) metricsContainer(w http.ResponseWriter, req *http.Request, name string) {
	if r.metricsStore == nil {
		writeJSON(w, http.StatusOK, apiResponse{OK: true, Data: map[string]interface{}{"labels": []int{}, "cpu": []float64{}, "memory": []float64{}}})
		return
	}

	from, to := parseTimeRange(req)
	resolution := parseResolution(req, 150)

	samples, err := r.metricsStore.QueryContainerMetrics(name, from, to, resolution)
	if err != nil {
		writeJSON(w, http.StatusInternalServerError, apiResponse{OK: false, Error: err.Error()})
		return
	}

	labels := make([]int64, len(samples))
	cpu := make([]float64, len(samples))
	memory := make([]float64, len(samples))

	for i, s := range samples {
		labels[i] = s.Timestamp
		cpu[i] = s.CPUPercent
		memory[i] = s.MemUsageMB
	}

	writeJSON(w, http.StatusOK, apiResponse{OK: true, Data: map[string]interface{}{
		"labels": labels,
		"cpu":    cpu,
		"memory": memory,
	}})
}

func (r *Router) metricsSysInfo(w http.ResponseWriter, _ *http.Request) {
	info := metrics.GetStaticInfo()
	writeJSON(w, http.StatusOK, apiResponse{OK: true, Data: info})
}

// parseTimeRange reads range or from/to query params.
func parseTimeRange(req *http.Request) (from, to time.Time) {
	to = time.Now()

	if rangeStr := req.URL.Query().Get("range"); rangeStr != "" {
		switch rangeStr {
		case "1h":
			from = to.Add(-1 * time.Hour)
		case "6h":
			from = to.Add(-6 * time.Hour)
		case "24h":
			from = to.Add(-24 * time.Hour)
		case "7d":
			from = to.Add(-7 * 24 * time.Hour)
		case "30d":
			from = to.Add(-30 * 24 * time.Hour)
		default:
			from = to.Add(-24 * time.Hour) // default 24h
		}
		return
	}

	if fromStr := req.URL.Query().Get("from"); fromStr != "" {
		if t, err := time.Parse(time.RFC3339, fromStr); err == nil {
			from = t
		}
	}
	if toStr := req.URL.Query().Get("to"); toStr != "" {
		if t, err := time.Parse(time.RFC3339, toStr); err == nil {
			to = t
		}
	}
	if from.IsZero() {
		from = to.Add(-24 * time.Hour)
	}
	return
}

// parseResolution reads the resolution query param.
func parseResolution(req *http.Request, defaultVal int) int {
	if v := req.URL.Query().Get("resolution"); v != "" {
		if n, err := strconv.Atoi(v); err == nil && n > 0 {
			return n
		}
	}
	return defaultVal
}

// --- Helpers ---

func hasSuffix(path, suffix string) bool { return strings.HasSuffix(path, suffix) }

func hasSubpath(path, prefix string) bool {
	rest := strings.TrimPrefix(path, prefix)
	return strings.Contains(rest, "/")
}

func trimSegment(path, prefix string) string {
	return strings.TrimPrefix(path, prefix)
}

func extractName(path, suffix string) string {
	s := strings.TrimPrefix(path, "/stacks/")
	return strings.TrimSuffix(s, suffix)
}

func writeJSON(w http.ResponseWriter, status int, v interface{}) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(status)
	if err := json.NewEncoder(w).Encode(v); err != nil {
		log.Printf("[ERROR] Failed to write JSON response: %v", err)
	}
}
