package proxmox

import (
	"bytes"
	"context"
	"crypto/tls"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"time"

	"github.com/pkg/errors"
)

// HTTPClient wraps http.Client with Proxmox-specific functionality
type HTTPClient struct {
	client     *http.Client
	endpoint   string
	ticket     string
	csrfToken  string
	token      string
	username   string
	password   string
}

// NewHTTPClient creates a new HTTP client for Proxmox API
// Implements FIPS 140-2 compliant TLS configuration per DoD/MilSpec requirements
func NewHTTPClient(endpoint string, insecureSkipTLS bool) *HTTPClient {
	// FIPS-approved cipher suites for TLS 1.3
	// Per NIST SP 800-53 SC-8 and NIST SP 800-171 3.13.1
	fipsCipherSuites := []uint16{
		tls.TLS_AES_256_GCM_SHA384,
		tls.TLS_CHACHA20_POLY1305_SHA256,
		tls.TLS_AES_128_GCM_SHA256,
	}

	tr := &http.Transport{
		TLSClientConfig: &tls.Config{
			InsecureSkipVerify: insecureSkipTLS,
			MinVersion:         tls.VersionTLS13, // TLS 1.3 minimum
			MaxVersion:         tls.VersionTLS13, // Only TLS 1.3
			CipherSuites:       fipsCipherSuites,
			PreferServerCipherSuites: true,
		},
		DisableKeepAlives: false,
		MaxIdleConns:      10,
		IdleConnTimeout:   30 * time.Second,
	}

	return &HTTPClient{
		client: &http.Client{
			Transport: tr,
			Timeout:   30 * time.Second,
		},
		endpoint: endpoint,
	}
}

// Authenticate authenticates with Proxmox API
func (c *HTTPClient) Authenticate(ctx context.Context) error {
	if c.token != "" {
		// Token authentication - token format: "user@realm!token-name=token-secret"
		return nil // Token will be used in requests
	}

	if c.username == "" || c.password == "" {
		return fmt.Errorf("username/password or token required")
	}

	authURL := fmt.Sprintf("%s/api2/json/access/ticket", c.endpoint)
	data := url.Values{}
	data.Set("username", c.username)
	data.Set("password", c.password)

	req, err := http.NewRequestWithContext(ctx, "POST", authURL, bytes.NewBufferString(data.Encode()))
	if err != nil {
		return errors.Wrap(err, "failed to create auth request")
	}
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

	resp, err := c.client.Do(req)
	if err != nil {
		return errors.Wrap(err, "failed to authenticate")
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		body, _ := io.ReadAll(resp.Body)
		return fmt.Errorf("authentication failed: %s - %s", resp.Status, string(body))
	}

	var result struct {
		Data struct {
			Ticket              string `json:"ticket"`
			CSRFPreventionToken string `json:"CSRFPreventionToken"`
		} `json:"data"`
	}

	if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
		return errors.Wrap(err, "failed to decode auth response")
	}

	c.ticket = result.Data.Ticket
	c.csrfToken = result.Data.CSRFPreventionToken

	return nil
}

// SetToken sets API token for authentication
func (c *HTTPClient) SetToken(token string) {
	c.token = token
}

// SetCredentials sets username and password for authentication
func (c *HTTPClient) SetCredentials(username, password string) {
	c.username = username
	c.password = password
}

// Do performs an HTTP request with authentication
func (c *HTTPClient) Do(ctx context.Context, method, path string, body interface{}) (*http.Response, error) {
	url := fmt.Sprintf("%s/api2/json%s", c.endpoint, path)

	var reqBody io.Reader
	if body != nil {
		jsonData, err := json.Marshal(body)
		if err != nil {
			return nil, errors.Wrap(err, "failed to marshal request body")
		}
		reqBody = bytes.NewBuffer(jsonData)
	}

	req, err := http.NewRequestWithContext(ctx, method, url, reqBody)
	if err != nil {
		return nil, errors.Wrap(err, "failed to create request")
	}

	// Set headers
	req.Header.Set("Content-Type", "application/json")
	if c.token != "" {
		// Token authentication
		req.Header.Set("Authorization", fmt.Sprintf("PVEAuthCookie=%s", c.token))
	} else if c.ticket != "" {
		// Ticket authentication
		req.AddCookie(&http.Cookie{
			Name:  "PVEAuthCookie",
			Value: c.ticket,
		})
		if c.csrfToken != "" && method != "GET" {
			req.Header.Set("CSRFPreventionToken", c.csrfToken)
		}
	}

	resp, err := c.client.Do(req)
	if err != nil {
		return nil, errors.Wrap(err, "request failed")
	}

	return resp, nil
}

// Get performs a GET request
func (c *HTTPClient) Get(ctx context.Context, path string, result interface{}) error {
	resp, err := c.Do(ctx, "GET", path, nil)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		body, _ := io.ReadAll(resp.Body)
		return fmt.Errorf("GET %s failed: %s - %s", path, resp.Status, string(body))
	}

	if result != nil {
		var apiResponse struct {
			Data interface{} `json:"data"`
		}
		if err := json.NewDecoder(resp.Body).Decode(&apiResponse); err != nil {
			return errors.Wrap(err, "failed to decode response")
		}
		// Unmarshal data into result
		dataBytes, _ := json.Marshal(apiResponse.Data)
		return json.Unmarshal(dataBytes, result)
	}

	return nil
}

// Post performs a POST request
func (c *HTTPClient) Post(ctx context.Context, path string, body, result interface{}) error {
	resp, err := c.Do(ctx, "POST", path, body)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		bodyBytes, _ := io.ReadAll(resp.Body)
		return fmt.Errorf("POST %s failed: %s - %s", path, resp.Status, string(bodyBytes))
	}

	if result != nil {
		var apiResponse struct {
			Data interface{} `json:"data"`
		}
		if err := json.NewDecoder(resp.Body).Decode(&apiResponse); err != nil {
			return errors.Wrap(err, "failed to decode response")
		}
		dataBytes, _ := json.Marshal(apiResponse.Data)
		return json.Unmarshal(dataBytes, result)
	}

	return nil
}

// Put performs a PUT request
func (c *HTTPClient) Put(ctx context.Context, path string, body, result interface{}) error {
	resp, err := c.Do(ctx, "PUT", path, body)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		bodyBytes, _ := io.ReadAll(resp.Body)
		return fmt.Errorf("PUT %s failed: %s - %s", path, resp.Status, string(bodyBytes))
	}

	if result != nil {
		var apiResponse struct {
			Data interface{} `json:"data"`
		}
		if err := json.NewDecoder(resp.Body).Decode(&apiResponse); err != nil {
			return errors.Wrap(err, "failed to decode response")
		}
		dataBytes, _ := json.Marshal(apiResponse.Data)
		return json.Unmarshal(dataBytes, result)
	}

	return nil
}

// Delete performs a DELETE request
func (c *HTTPClient) Delete(ctx context.Context, path string) error {
	resp, err := c.Do(ctx, "DELETE", path, nil)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		bodyBytes, _ := io.ReadAll(resp.Body)
		return fmt.Errorf("DELETE %s failed: %s - %s", path, resp.Status, string(bodyBytes))
	}

	return nil
}

