package providers

import (
	"context"
	"os"
	"strconv"
	"strings"

	"github.com/wangjia/pangolin/server/internal/provision"
)

// hetznerAdapter implements provision.CloudAdapter against the Hetzner Cloud
// API v1. Premium pool: stable vendor, good native IPs, floating-IP capable.
//
// Credentials: PROVISION_HETZNER_API_TOKEN (env, independent secret).
// Default image: PROVISION_HETZNER_IMAGE (defaults to "debian-12").
type hetznerAdapter struct {
	c     *httpClient
	image string
}

func newHetznerFromEnv() (provision.CloudAdapter, error) {
	token, err := secret("PROVISION_HETZNER_API_TOKEN")
	if err != nil {
		return nil, err
	}
	image := os.Getenv("PROVISION_HETZNER_IMAGE")
	if image == "" {
		image = "debian-12"
	}
	return &hetznerAdapter{
		c:     newHTTPClient("https://api.hetzner.cloud/v1", token),
		image: image,
	}, nil
}

func (a *hetznerAdapter) Kind() string            { return "hetzner" }
func (a *hetznerAdapter) SupportsElasticIP() bool { return true } // floating IPs

func (a *hetznerAdapter) CreateInstance(ctx context.Context, in provision.CreateInput) (*provision.Instance, error) {
	body := map[string]any{
		"name":               sanitizeName(in.Label),
		"server_type":        in.Plan,
		"image":              a.image,
		"location":           in.Region,
		"user_data":          in.UserData, // Hetzner accepts raw cloud-init
		"start_after_create": true,
		"labels":             labelsFromTags(in.Tags),
	}
	if len(in.SSHKeyIDs) > 0 {
		body["ssh_keys"] = in.SSHKeyIDs
	}
	var resp struct {
		Server struct {
			ID        int64 `json:"id"`
			PublicNet struct {
				IPv4 struct {
					IP string `json:"ip"`
				} `json:"ipv4"`
			} `json:"public_net"`
			Datacenter struct {
				Location struct {
					Name string `json:"name"`
				} `json:"location"`
			} `json:"datacenter"`
		} `json:"server"`
	}
	if err := a.c.do(ctx, "POST", "/servers", body, &resp); err != nil {
		return nil, err
	}
	return &provision.Instance{
		ID:     strconv.FormatInt(resp.Server.ID, 10),
		IP:     resp.Server.PublicNet.IPv4.IP,
		Region: resp.Server.Datacenter.Location.Name,
	}, nil
}

func (a *hetznerAdapter) DestroyInstance(ctx context.Context, instanceID string) error {
	err := a.c.do(ctx, "DELETE", "/servers/"+instanceID, nil, nil)
	if err != nil && isNotFound(err) {
		return nil // idempotent
	}
	return err
}

func (a *hetznerAdapter) AttachIP(ctx context.Context, instanceID string) (string, error) {
	id, err := strconv.ParseInt(instanceID, 10, 64)
	if err != nil {
		return "", err
	}
	var resp struct {
		FloatingIP struct {
			ID int64  `json:"id"`
			IP string `json:"ip"`
		} `json:"floating_ip"`
	}
	if err := a.c.do(ctx, "POST", "/floating_ips", map[string]any{
		"type":        "ipv4",
		"server":      id,
		"description": "rotate-" + instanceID,
	}, &resp); err != nil {
		return "", err
	}
	return resp.FloatingIP.IP, nil
}

func (a *hetznerAdapter) ListRegions(ctx context.Context) ([]provision.Region, error) {
	var resp struct {
		Locations []struct {
			Name    string `json:"name"`
			Country string `json:"country"`
			City    string `json:"city"`
		} `json:"locations"`
	}
	if err := a.c.do(ctx, "GET", "/locations", nil, &resp); err != nil {
		return nil, err
	}
	out := make([]provision.Region, 0, len(resp.Locations))
	for _, l := range resp.Locations {
		out = append(out, provision.Region{ID: l.Name, Country: l.Country, City: l.City})
	}
	return out, nil
}

// --- helpers shared with vultr ---

func atoiDefault(s string, def int) int {
	if s == "" {
		return def
	}
	n, err := strconv.Atoi(s)
	if err != nil {
		return def
	}
	return n
}

func isNotFound(err error) bool {
	return err != nil && (strings.Contains(err.Error(), "status 404") || strings.Contains(err.Error(), "not_found"))
}

// sanitizeName makes a UUID acceptable as a Hetzner server name (RFC1123-ish).
func sanitizeName(s string) string {
	s = strings.ToLower(s)
	return "node-" + s
}

func labelsFromTags(tags []string) map[string]string {
	m := map[string]string{}
	for i, t := range tags {
		m["tag"+strconv.Itoa(i)] = t
	}
	return m
}
