package cloudflare

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

const (
	baseURL = "https://api.cloudflare.com/client/v4"
)

// Client handles Cloudflare API interactions
type Client struct {
	apiToken string
	zoneID   string
	http     *http.Client
}

// NewClient creates a new Cloudflare API client
func NewClient() *Client {
	return &Client{
		apiToken: os.Getenv("CLOUDFLARE_API_TOKEN"),
		zoneID:   os.Getenv("CLOUDFLARE_ZONE_ID"),
		http: &http.Client{
			Timeout: 30 * time.Second,
		},
	}
}

// NewClientWithConfig creates a client with custom config
func NewClientWithConfig(apiToken, zoneID string) *Client {
	return &Client{
		apiToken: apiToken,
		zoneID:   zoneID,
		http: &http.Client{
			Timeout: 30 * time.Second,
		},
	}
}

// Zone represents a Cloudflare zone
type Zone struct {
	ID     string `json:"id"`
	Name   string `json:"name"`
	Status string `json:"status"`
}

// ZonesResponse is the response from /zones endpoint
type ZonesResponse struct {
	Success  bool     `json:"success"`
	Errors   []Error  `json:"errors"`
	Messages []string `json:"messages"`
	Result   []Zone   `json:"result"`
}

// PurgeResponse is the response from cache purge endpoints
type PurgeResponse struct {
	Success  bool     `json:"success"`
	Errors   []Error  `json:"errors"`
	Messages []string `json:"messages"`
	Result   struct {
		ID string `json:"id"`
	} `json:"result"`
}

// Error represents a Cloudflare API error
type Error struct {
	Code    int    `json:"code"`
	Message string `json:"message"`
}

// doRequest executes an HTTP request to Cloudflare API
func (c *Client) doRequest(method, endpoint string, body interface{}) ([]byte, error) {
	var reqBody io.Reader
	if body != nil {
		jsonBody, err := json.Marshal(body)
		if err != nil {
			return nil, fmt.Errorf("failed to marshal request body: %w", err)
		}
		reqBody = bytes.NewBuffer(jsonBody)
	}

	req, err := http.NewRequest(method, baseURL+endpoint, reqBody)
	if err != nil {
		return nil, fmt.Errorf("failed to create request: %w", err)
	}

	req.Header.Set("Authorization", "Bearer "+c.apiToken)
	req.Header.Set("Content-Type", "application/json")

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

	respBody, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("failed to read response: %w", err)
	}

	if resp.StatusCode >= 400 {
		return nil, fmt.Errorf("API error (status %d): %s", resp.StatusCode, string(respBody))
	}

	return respBody, nil
}

// GetZones returns all zones for the account
func (c *Client) GetZones() ([]Zone, error) {
	respBody, err := c.doRequest("GET", "/zones", nil)
	if err != nil {
		return nil, err
	}

	var response ZonesResponse
	if err := json.Unmarshal(respBody, &response); err != nil {
		return nil, fmt.Errorf("failed to parse response: %w", err)
	}

	if !response.Success {
		if len(response.Errors) > 0 {
			return nil, fmt.Errorf("API error: %s", response.Errors[0].Message)
		}
		return nil, fmt.Errorf("unknown API error")
	}

	return response.Result, nil
}

// PurgeAll purges all cached content for the zone
func (c *Client) PurgeAll() (*PurgeResponse, error) {
	endpoint := fmt.Sprintf("/zones/%s/purge_cache", c.zoneID)
	body := map[string]bool{"purge_everything": true}

	respBody, err := c.doRequest("POST", endpoint, body)
	if err != nil {
		return nil, err
	}

	var response PurgeResponse
	if err := json.Unmarshal(respBody, &response); err != nil {
		return nil, fmt.Errorf("failed to parse response: %w", err)
	}

	return &response, nil
}

// PurgeByURLs purges specific URLs from cache
func (c *Client) PurgeByURLs(urls []string) (*PurgeResponse, error) {
	endpoint := fmt.Sprintf("/zones/%s/purge_cache", c.zoneID)
	body := map[string][]string{"files": urls}

	respBody, err := c.doRequest("POST", endpoint, body)
	if err != nil {
		return nil, err
	}

	var response PurgeResponse
	if err := json.Unmarshal(respBody, &response); err != nil {
		return nil, fmt.Errorf("failed to parse response: %w", err)
	}

	return &response, nil
}

// PurgeByTags purges content by cache tags (Enterprise only)
func (c *Client) PurgeByTags(tags []string) (*PurgeResponse, error) {
	endpoint := fmt.Sprintf("/zones/%s/purge_cache", c.zoneID)
	body := map[string][]string{"tags": tags}

	respBody, err := c.doRequest("POST", endpoint, body)
	if err != nil {
		return nil, err
	}

	var response PurgeResponse
	if err := json.Unmarshal(respBody, &response); err != nil {
		return nil, fmt.Errorf("failed to parse response: %w", err)
	}

	return &response, nil
}

// PurgeByHosts purges content by hostnames
func (c *Client) PurgeByHosts(hosts []string) (*PurgeResponse, error) {
	endpoint := fmt.Sprintf("/zones/%s/purge_cache", c.zoneID)
	body := map[string][]string{"hosts": hosts}

	respBody, err := c.doRequest("POST", endpoint, body)
	if err != nil {
		return nil, err
	}

	var response PurgeResponse
	if err := json.Unmarshal(respBody, &response); err != nil {
		return nil, fmt.Errorf("failed to parse response: %w", err)
	}

	return &response, nil
}
