package handlers

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

	"alpine-router/config"
	"alpine-router/network"
)

type apiResp struct {
	Success bool        `json:"success"`
	Data    interface{} `json:"data,omitempty"`
	Error   string      `json:"error,omitempty"`
}

func writeJSON(w http.ResponseWriter, status int, v interface{}) {
	w.Header().Set("Content-Type", "application/json; charset=utf-8")
	w.WriteHeader(status)
	_ = json.NewEncoder(w).Encode(v)
}

func ok(w http.ResponseWriter, data interface{}) {
	writeJSON(w, http.StatusOK, apiResp{Success: true, Data: data})
}

func fail(w http.ResponseWriter, status int, msg string) {
	writeJSON(w, status, apiResp{Error: msg})
}

func HandleInterfaces(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet {
		fail(w, http.StatusMethodNotAllowed, "method not allowed")
		return
	}

	names, err := network.GetInterfaces()
	if err != nil {
		fail(w, http.StatusInternalServerError, err.Error())
		return
	}

	fileCfg, _ := network.ParseConfig()

	type iface struct {
		*network.InterfaceStats
		Pending bool   `json:"pending"`
		Label   string `json:"label,omitempty"`
	}

	appCfg, _ := config.Load()

	result := make([]iface, 0, len(names))
	existingNames := map[string]bool{}
	for _, name := range names {
		existingNames[name] = true
		s, err := network.GetInterfaceStats(name)
		if err != nil {
			continue
		}
		if cfg, ok := fileCfg[name]; ok {
			s.Mode = cfg.Mode
		}
		hasPending := network.GetPendingConfig(name) != nil
		label := ""
		if appCfg != nil && appCfg.Interfaces != nil {
			if ic, ok := appCfg.Interfaces[name]; ok {
				label = ic.Label
			}
		}
		result = append(result, iface{s, hasPending, label})
	}

	// Also include pending VLAN configs not yet present in the system.
	for name, cfg := range network.GetAllPending() {
		if existingNames[name] || !network.IsVLAN(name) {
			continue
		}
		s := &network.InterfaceStats{
			Name:  name,
			State: "unknown",
			Mode:  cfg.Mode,
			IPv6:  []string{},
		}
		if cfg.Mode == "static" {
			s.IPv4 = cfg.Address
			s.IPv4Mask = cfg.Netmask
			s.Gateway = cfg.Gateway
		}
		label := cfg.Label
		if label == "" && appCfg != nil && appCfg.Interfaces != nil {
			if ic, ok := appCfg.Interfaces[name]; ok {
				label = ic.Label
			}
		}
		result = append(result, iface{s, true, label})
	}

	ok(w, result)
}

func HandleInterfaceSingle(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet {
		fail(w, http.StatusMethodNotAllowed, "method not allowed")
		return
	}
	name := strings.TrimPrefix(r.URL.Path, "/api/interfaces/")
	s, err := network.GetInterfaceStats(name)
	if err != nil {
		fail(w, http.StatusInternalServerError, err.Error())
		return
	}
	ok(w, s)
}

func HandleInterfaceAction(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		fail(w, http.StatusMethodNotAllowed, "method not allowed")
		return
	}

	suffix := strings.TrimPrefix(r.URL.Path, "/api/interfaces/")
	parts := strings.SplitN(suffix, "/", 2)
	if len(parts) != 2 {
		fail(w, http.StatusBadRequest, "invalid path")
		return
	}
	name, action := parts[0], parts[1]

	var err error
	switch action {
	case "up":
		err = network.IfUp(name)
	case "down":
		err = network.IfDown(name)
	case "restart":
		err = network.IfRestart(name)
	case "delete":
		if !network.IsVLAN(name) {
			fail(w, http.StatusBadRequest, "delete is only supported for VLAN interfaces")
			return
		}
		network.ClearPendingConfig(name)
		err = network.DeleteVLAN(name)
	default:
		fail(w, http.StatusBadRequest, "unknown action: "+action)
		return
	}

	if err != nil {
		fail(w, http.StatusInternalServerError, err.Error())
		return
	}
	ok(w, map[string]string{"message": action + " ok"})
}

func HandleConfig(w http.ResponseWriter, r *http.Request) {
	name := strings.TrimPrefix(r.URL.Path, "/api/config/")
	if name == "" {
		fail(w, http.StatusBadRequest, "interface name required")
		return
	}

	switch r.Method {
	case http.MethodGet:
		appCfg, _ := config.Load()
		label := ""
		if appCfg != nil && appCfg.Interfaces != nil {
			if ic, ok2 := appCfg.Interfaces[name]; ok2 {
				label = ic.Label
			}
		}
		if cfg := network.GetPendingConfig(name); cfg != nil {
			if cfg.Label != "" {
				label = cfg.Label
			}
			ok(w, map[string]interface{}{"config": cfg, "pending": true, "label": label})
			return
		}
		fileCfg, err := network.ParseConfig()
		if err != nil {
			fail(w, http.StatusInternalServerError, err.Error())
			return
		}
		if cfg, exists := fileCfg[name]; exists {
			ok(w, map[string]interface{}{"config": cfg, "pending": false, "label": label})
		} else {
			ok(w, map[string]interface{}{
				"config":  &network.InterfaceConfig{Name: name, Auto: true, Mode: "dhcp", Extra: map[string]string{}},
				"pending": false,
				"label":   label,
			})
		}

	case http.MethodPost:
		var cfg network.InterfaceConfig
		if err := json.NewDecoder(r.Body).Decode(&cfg); err != nil {
			fail(w, http.StatusBadRequest, "invalid json: "+err.Error())
			return
		}
		cfg.Name = name
		if cfg.Extra == nil {
			cfg.Extra = map[string]string{}
		}
		network.SetPendingConfig(&cfg)

		appCfg, err := config.Load()
		if err != nil {
			fail(w, http.StatusInternalServerError, "load config: "+err.Error())
			return
		}
		if appCfg.Interfaces == nil {
			appCfg.Interfaces = map[string]*config.InterfaceConfig{}
		}
		appCfg.Interfaces[name] = &config.InterfaceConfig{
			Label:   cfg.Label,
			Auto:    cfg.Auto,
			Mode:    cfg.Mode,
			Address: cfg.Address,
			Netmask: cfg.Netmask,
			Gateway: cfg.Gateway,
			DNS:     cfg.DNS,
			Extra:   cfg.Extra,
		}
		if err := config.Save(appCfg); err != nil {
			fail(w, http.StatusInternalServerError, "save config.yaml: "+err.Error())
			return
		}

		ok(w, map[string]string{"message": "saved as pending"})

	case http.MethodDelete:
		network.ClearPendingConfig(name)
		ok(w, map[string]string{"message": "pending cleared"})

	default:
		fail(w, http.StatusMethodNotAllowed, "method not allowed")
	}
}

func HandlePending(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet {
		fail(w, http.StatusMethodNotAllowed, "method not allowed")
		return
	}
	p := network.GetAllPending()
	names := make([]string, 0, len(p))
	for n := range p {
		names = append(names, n)
	}
	ok(w, names)
}

func HandleApply(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		fail(w, http.StatusMethodNotAllowed, "method not allowed")
		return
	}

	errs := network.ApplyPending()
	if len(errs) > 0 {
		msgs := map[string]string{}
		for k, e := range errs {
			msgs[k] = e.Error()
		}
		writeJSON(w, http.StatusInternalServerError, apiResp{Error: "partial failure", Data: msgs})
		return
	}
	ok(w, map[string]string{"message": "applied"})
}

func HandleConfigYAML(w http.ResponseWriter, r *http.Request) {
	switch r.Method {
	case http.MethodGet:
		data, err := config.Load()
		if err != nil {
			fail(w, http.StatusInternalServerError, err.Error())
			return
		}
		ok(w, data)

	case http.MethodPut:
		var newCfg config.AppConfig
		if err := json.NewDecoder(r.Body).Decode(&newCfg); err != nil {
			fail(w, http.StatusBadRequest, "invalid json: "+err.Error())
			return
		}
		config.EnsureDefaults(&newCfg)
		if err := config.Save(&newCfg); err != nil {
			fail(w, http.StatusInternalServerError, "save: "+err.Error())
			return
		}
		ok(w, map[string]string{"message": "config updated"})

	default:
		fail(w, http.StatusMethodNotAllowed, "method not allowed")
	}
}