package services

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

const hostingerBaseURL = "https://developers.hostinger.com/api"

// HostingerClient es el cliente HTTP para la API de Hostinger.
type HostingerClient struct {
	token      string
	httpClient *http.Client
}

// NewHostingerClient crea un cliente con el token Bearer.
func NewHostingerClient(token string) *HostingerClient {
	return &HostingerClient{
		token: token,
		httpClient: &http.Client{
			Timeout: 10 * time.Second,
		},
	}
}

// getRaw realiza una petición GET autenticada y devuelve el body crudo.
// El contexto permite cancelar la llamada si el caller abandona la petición.
func (c *HostingerClient) getRaw(ctx context.Context, path string) ([]byte, error) {
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, hostingerBaseURL+path, nil)
	if err != nil {
		return nil, fmt.Errorf("hostinger: crear request: %w", err)
	}
	req.Header.Set("Authorization", "Bearer "+c.token)
	req.Header.Set("Content-Type", "application/json")

	resp, err := c.httpClient.Do(req)
	if err != nil {
		return nil, fmt.Errorf("hostinger: ejecutar request: %w", err)
	}
	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("hostinger: leer body: %w", err)
	}

	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		return nil, fmt.Errorf("hostinger: status %d – %s", resp.StatusCode, string(body))
	}
	return body, nil
}

// writeRaw envía una petición HTTP autenticada con método y body opcionales.
func (c *HostingerClient) writeRaw(ctx context.Context, method, path string, payload interface{}) ([]byte, error) {
	var reqBody io.Reader
	if payload != nil {
		b, err := json.Marshal(payload)
		if err != nil {
			return nil, fmt.Errorf("hostinger: serializar body: %w", err)
		}
		reqBody = bytes.NewReader(b)
	}
	req, err := http.NewRequestWithContext(ctx, method, hostingerBaseURL+path, reqBody)
	if err != nil {
		return nil, fmt.Errorf("hostinger: crear request: %w", err)
	}
	req.Header.Set("Authorization", "Bearer "+c.token)
	req.Header.Set("Content-Type", "application/json")
	resp, err := c.httpClient.Do(req)
	if err != nil {
		return nil, fmt.Errorf("hostinger: ejecutar request: %w", err)
	}
	defer resp.Body.Close()
	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("hostinger: leer body: %w", err)
	}
	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		return nil, fmt.Errorf("hostinger: status %d – %s", resp.StatusCode, string(body))
	}
	return body, nil
}

// get realiza una petición GET autenticada y decodifica el cuerpo en dest.
func (c *HostingerClient) get(path string, dest interface{}) error {
	body, err := c.getRaw(context.Background(), path)
	if err != nil {
		return err
	}
	if dest != nil {
		if err := json.Unmarshal(body, dest); err != nil {
			return fmt.Errorf("hostinger: decodificar respuesta: %w", err)
		}
	}
	return nil
}

// ─── Tipos de respuesta ──────────────────────────────────────────────────────

// HostingerIPAddress representa una dirección IP de un VPS (IPv4 o IPv6).
type HostingerIPAddress struct {
	ID      int    `json:"id"`
	Address string `json:"address"`
	PTR     string `json:"ptr"`
}

// HostingerVPS representa una máquina virtual VPS.
type HostingerVPS struct {
	ID             int                  `json:"id"`
	Hostname       string               `json:"hostname"`
	State          string               `json:"state"`
	Plan           string               `json:"plan"`
	DataCenter     string               `json:"data_center"`
	CPU            int                  `json:"cpus"`
	RAMBytes       int64                `json:"memory"`
	DiskBytes      int64                `json:"disk"`
	IPV4           []HostingerIPAddress `json:"ipv4"`
	IPV6           []HostingerIPAddress `json:"ipv6"`
	SubscriptionID string               `json:"subscription_id"`
}

// HostingerDomain representa un dominio en el portafolio.
type HostingerDomain struct {
	Domain       string `json:"domain"`
	Status       string `json:"status"`
	ExpiresAt    string `json:"expires_at"`
	RegisteredAt string `json:"registered_at"`
}

// HostingerDNSRecord representa un registro DNS aplanado para la vista.
type HostingerDNSRecord struct {
	Type    string `json:"type"`
	Name    string `json:"name"`
	Content string `json:"content"`
	TTL     int    `json:"ttl"`
}

// HostingerNameServers contiene los nameservers de un dominio.
type HostingerNameServers struct {
	NS1 string `json:"ns1"`
	NS2 string `json:"ns2"`
	NS3 string `json:"ns3"`
	NS4 string `json:"ns4"`
}

// HostingerDNSZoneInput es un grupo de registros para PUT /dns/v1/zones/{domain}.
type HostingerDNSZoneInput struct {
	Name    string                  `json:"name"`
	Type    string                  `json:"type"`
	TTL     int                     `json:"ttl,omitempty"`
	Records []HostingerDNSZoneValue `json:"records"`
}

// HostingerDNSZoneValue es el valor de contenido de un registro DNS.
type HostingerDNSZoneValue struct {
	Content string `json:"content"`
}

// HostingerSubscription representa una suscripción de facturación.
type HostingerSubscription struct {
	ID                string `json:"id"`
	Name              string `json:"name"`
	Status            string `json:"status"`
	BillingPeriod     int    `json:"billing_period"`
	BillingPeriodUnit string `json:"billing_period_unit"`
	CurrencyCode      string `json:"currency_code"`
	TotalPrice        int    `json:"total_price"`
	RenewalPrice      int    `json:"renewal_price"`
	IsAutoRenewed     bool   `json:"is_auto_renewed"`
	CreatedAt         string `json:"created_at"`
	ExpiresAt         string `json:"expires_at"`
	NextBillingAt     string `json:"next_billing_at"`
}

// HostingerHosting representa una cuenta de hosting.
type HostingerHosting struct {
	Domain        string `json:"domain"`
	VhostType     string `json:"vhost_type"`
	IsEnabled     bool   `json:"is_enabled"`
	Username      string `json:"username"`
	ClientID      int    `json:"client_id"`
	OrderID       int    `json:"order_id"`
	CreatedAt     string `json:"created_at"`
	RootDirectory string `json:"root_directory"`
	ParentDomain  string `json:"parent_domain"`
}

// ─── Métodos de la API ───────────────────────────────────────────────────────

// GetVPSByID obtiene una VM específica por su ID.
func (c *HostingerClient) GetVPSByID(vmID int) (*HostingerVPS, error) {
	path := fmt.Sprintf("/vps/v1/virtual-machines/%d", vmID)
	body, err := c.getRaw(context.Background(), path)
	if err != nil {
		return nil, err
	}
	// Intentar primero como objeto directo
	var vps HostingerVPS
	if err := json.Unmarshal(body, &vps); err == nil && vps.ID != 0 {
		return &vps, nil
	}
	// Fallback: respuesta envuelta en {"data": {...}}
	var wrapped struct {
		Data HostingerVPS `json:"data"`
	}
	if err := json.Unmarshal(body, &wrapped); err == nil && wrapped.Data.ID != 0 {
		return &wrapped.Data, nil
	}
	return nil, fmt.Errorf("hostinger: no se pudo interpretar respuesta VPS (body: %s)", string(body))
}

// GetVPSList obtiene la lista de VPS/VMs.
func (c *HostingerClient) GetVPSList() ([]HostingerVPS, error) {
	body, err := c.getRaw(context.Background(), "/vps/v1/virtual-machines")
	if err != nil {
		return nil, err
	}
	var wrapped struct {
		Data []HostingerVPS `json:"data"`
	}
	if err := json.Unmarshal(body, &wrapped); err == nil && wrapped.Data != nil {
		return wrapped.Data, nil
	}
	var arr []HostingerVPS
	if err := json.Unmarshal(body, &arr); err != nil {
		return nil, fmt.Errorf("hostinger: no se pudo interpretar respuesta VPS: %w", err)
	}
	return arr, nil
}

// GetDomains obtiene el portafolio de dominios.
func (c *HostingerClient) GetDomains() ([]HostingerDomain, error) {
	body, err := c.getRaw(context.Background(), "/domains/v1/portfolio")
	if err != nil {
		return nil, err
	}
	var wrapped struct {
		Data []HostingerDomain `json:"data"`
	}
	if err := json.Unmarshal(body, &wrapped); err == nil && wrapped.Data != nil {
		return wrapped.Data, nil
	}
	var arr []HostingerDomain
	if err := json.Unmarshal(body, &arr); err != nil {
		return nil, fmt.Errorf("hostinger: no se pudo interpretar respuesta dominios: %w", err)
	}
	return arr, nil
}

// hostingerDNSContent es el valor individual de un registro DNS.
type hostingerDNSContent struct {
	Content    string `json:"content"`
	IsDisabled bool   `json:"is_disabled"`
}

// hostingerDNSEntry es un grupo de registros del mismo tipo/nombre desde la API.
type hostingerDNSEntry struct {
	Name    string                `json:"name"`
	TTL     int                   `json:"ttl"`
	Type    string                `json:"type"`
	Records []hostingerDNSContent `json:"records"`
}

// GetDNSRecords obtiene los registros DNS de un dominio y los aplana para la vista.
// Endpoint: GET /dns/v1/zones/{domain} — array de {name, ttl, type, records:[{content}]}
func (c *HostingerClient) GetDNSRecords(domain string) ([]HostingerDNSRecord, error) {
	path := fmt.Sprintf("/dns/v1/zones/%s", domain)
	body, err := c.getRaw(context.Background(), path)
	if err != nil {
		return nil, err
	}
	var entries []hostingerDNSEntry
	if err := json.Unmarshal(body, &entries); err != nil {
		return nil, fmt.Errorf("hostinger: no se pudo interpretar respuesta DNS: %w", err)
	}
	var result []HostingerDNSRecord
	for _, e := range entries {
		for _, r := range e.Records {
			result = append(result, HostingerDNSRecord{
				Type:    e.Type,
				Name:    e.Name,
				Content: r.Content,
				TTL:     e.TTL,
			})
		}
	}
	if result == nil {
		result = []HostingerDNSRecord{}
	}
	return result, nil
}

// GetDomainNameServers obtiene los nameservers del dominio desde el portafolio.
// Endpoint: GET /domains/v1/portfolio/{domain}
func (c *HostingerClient) GetDomainNameServers(domain string) (*HostingerNameServers, error) {
	path := fmt.Sprintf("/domains/v1/portfolio/%s", domain)
	body, err := c.getRaw(context.Background(), path)
	if err != nil {
		return nil, err
	}
	var resp struct {
		NameServers HostingerNameServers `json:"name_servers"`
	}
	if err := json.Unmarshal(body, &resp); err != nil {
		return nil, fmt.Errorf("hostinger: no se pudo interpretar nameservers: %w", err)
	}
	return &resp.NameServers, nil
}

// GetSubscriptionByID obtiene una suscripción de billing por su ID.
func (c *HostingerClient) GetSubscriptionByID(subID string) (*HostingerSubscription, error) {
	path := fmt.Sprintf("/billing/v1/subscriptions/%s", subID)
	body, err := c.getRaw(context.Background(), path)
	if err != nil {
		return nil, err
	}
	var sub HostingerSubscription
	if err := json.Unmarshal(body, &sub); err != nil {
		return nil, fmt.Errorf("hostinger: no se pudo interpretar suscripción: %w", err)
	}
	return &sub, nil
}

// GetOrders obtiene las suscripciones de facturación.
func (c *HostingerClient) GetOrders() ([]HostingerSubscription, error) {
	body, err := c.getRaw(context.Background(), "/billing/v1/subscriptions")
	if err != nil {
		return nil, err
	}
	var wrapped struct {
		Data []HostingerSubscription `json:"data"`
	}
	if err := json.Unmarshal(body, &wrapped); err == nil && wrapped.Data != nil {
		return wrapped.Data, nil
	}
	var arr []HostingerSubscription
	if err := json.Unmarshal(body, &arr); err != nil {
		return nil, fmt.Errorf("hostinger: no se pudo interpretar respuesta subscriptions: %w", err)
	}
	return arr, nil
}

// ─── DNS: escritura ─────────────────────────────────────────────────────────

// UpdateDNSZone agrega/actualiza registros en la zona DNS.
// overwrite=true reemplaza registros del mismo tipo/nombre; false los agrega.
func (c *HostingerClient) UpdateDNSZone(domain string, overwrite bool, zone []HostingerDNSZoneInput) error {
	path := fmt.Sprintf("/dns/v1/zones/%s", domain)
	_, err := c.writeRaw(context.Background(), http.MethodPut, path,
		map[string]interface{}{"overwrite": overwrite, "zone": zone})
	return err
}

// ResetDNS restaura la zona DNS de un dominio a sus valores por defecto.
func (c *HostingerClient) ResetDNS(domain string) error {
	path := fmt.Sprintf("/dns/v1/zones/%s/reset", domain)
	_, err := c.writeRaw(context.Background(), http.MethodPost, path, map[string]bool{"sync": true})
	return err
}

// ─── Domains: escritura ──────────────────────────────────────────────────────

// UpdateNameservers actualiza los nameservers de un dominio.
func (c *HostingerClient) UpdateNameservers(domain, ns1, ns2, ns3, ns4 string) error {
	path := fmt.Sprintf("/domains/v1/portfolio/%s/nameservers", domain)
	_, err := c.writeRaw(context.Background(), http.MethodPut, path,
		map[string]string{"ns1": ns1, "ns2": ns2, "ns3": ns3, "ns4": ns4})
	return err
}

// SetDomainLock activa (PUT) o desactiva (DELETE) el bloqueo del dominio.
func (c *HostingerClient) SetDomainLock(domain string, enable bool) error {
	path := fmt.Sprintf("/domains/v1/portfolio/%s/domain-lock", domain)
	method := http.MethodPut
	if !enable {
		method = http.MethodDelete
	}
	_, err := c.writeRaw(context.Background(), method, path, nil)
	return err
}

// SetPrivacyProtection activa (PUT) o desactiva (DELETE) la protección WHOIS.
func (c *HostingerClient) SetPrivacyProtection(domain string, enable bool) error {
	path := fmt.Sprintf("/domains/v1/portfolio/%s/privacy-protection", domain)
	method := http.MethodPut
	if !enable {
		method = http.MethodDelete
	}
	_, err := c.writeRaw(context.Background(), method, path, nil)
	return err
}

// ─── VPS: acciones ───────────────────────────────────────────────────────────

// VPSAction ejecuta una acción en una VM: start | stop | restart.
func (c *HostingerClient) VPSAction(vmID int, action string) error {
	path := fmt.Sprintf("/vps/v1/virtual-machines/%d/%s", vmID, action)
	_, err := c.writeRaw(context.Background(), http.MethodPost, path, nil)
	return err
}

// SetVPSRootPassword cambia la contraseña root de una VM.
func (c *HostingerClient) SetVPSRootPassword(vmID int, password string) error {
	path := fmt.Sprintf("/vps/v1/virtual-machines/%d/root-password", vmID)
	_, err := c.writeRaw(context.Background(), http.MethodPut, path, map[string]string{"password": password})
	return err
}

// SetVPSHostname cambia el hostname de una VM.
func (c *HostingerClient) SetVPSHostname(vmID int, hostname string) error {
	path := fmt.Sprintf("/vps/v1/virtual-machines/%d/hostname", vmID)
	_, err := c.writeRaw(context.Background(), http.MethodPut, path, map[string]string{"hostname": hostname})
	return err
}

// GetVPSMetrics obtiene las métricas de uso de una VM.
// dateFrom y dateTo deben estar en formato RFC3339, p. ej. "2025-01-01T00:00:00Z".
func (c *HostingerClient) GetVPSMetrics(vmID int, dateFrom, dateTo string) (json.RawMessage, error) {
	path := fmt.Sprintf("/vps/v1/virtual-machines/%d/metrics?date_from=%s&date_to=%s",
		vmID, dateFrom, dateTo)
	body, err := c.getRaw(context.Background(), path)
	if err != nil {
		return nil, err
	}
	return json.RawMessage(body), nil
}

// GetVPSBackups obtiene los backups de una VM.
func (c *HostingerClient) GetVPSBackups(vmID int) (json.RawMessage, error) {
	path := fmt.Sprintf("/vps/v1/virtual-machines/%d/backups", vmID)
	body, err := c.getRaw(context.Background(), path)
	if err != nil {
		return nil, err
	}
	return json.RawMessage(body), nil
}

// ─── Billing: acciones ───────────────────────────────────────────────────────

// ToggleAutoRenewal activa (true) o desactiva la autorenovación de una suscripción.
func (c *HostingerClient) ToggleAutoRenewal(subscriptionID string, enable bool) (*HostingerSubscription, error) {
	var path, method string
	if enable {
		path = fmt.Sprintf("/billing/v1/subscriptions/%s/auto-renewal/enable", subscriptionID)
		method = http.MethodPatch
	} else {
		path = fmt.Sprintf("/billing/v1/subscriptions/%s/auto-renewal/disable", subscriptionID)
		method = http.MethodDelete
	}
	body, err := c.writeRaw(context.Background(), method, path, nil)
	if err != nil {
		return nil, err
	}
	var sub HostingerSubscription
	if err := json.Unmarshal(body, &sub); err != nil {
		return nil, fmt.Errorf("hostinger: no se pudo interpretar respuesta: %w", err)
	}
	return &sub, nil
}

// GetHostingAccounts obtiene las cuentas de hosting.
// Endpoint: GET /hosting/v1/websites
func (c *HostingerClient) GetHostingAccounts() ([]HostingerHosting, error) {
	body, err := c.getRaw(context.Background(), "/hosting/v1/websites")
	if err != nil {
		return nil, err
	}
	var wrapped struct {
		Data []HostingerHosting `json:"data"`
	}
	if err := json.Unmarshal(body, &wrapped); err == nil && wrapped.Data != nil {
		return wrapped.Data, nil
	}
	var arr []HostingerHosting
	if err := json.Unmarshal(body, &arr); err != nil {
		return nil, fmt.Errorf("hostinger: no se pudo interpretar respuesta hosting: %w", err)
	}
	return arr, nil
}
