package godaddy

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"strconv"
	"strings"
	"time"

	"security-center-backend/internal/modules/caasm/collector"
	model "security-center-backend/internal/modules/caasm/model"
)

const (
	baseURLProduction = "https://api.godaddy.com/v1"
	baseURLOTE        = "https://api.ote-godaddy.com/v1"
)

type domainResponse struct {
	Domain      string     `json:"domain"`
	Expires     *time.Time `json:"expires"`
	Status      string     `json:"status"`
	NameServers []string   `json:"nameServers"`
}

type recordResponse struct {
	Type     string `json:"type"`
	Name     string `json:"name"`
	Data     string `json:"data"`
	TTL      int    `json:"ttl"`
	Priority int    `json:"priority,omitempty"`
	Port     int    `json:"port,omitempty"`
	Weight   int    `json:"weight,omitempty"`
	Protocol string `json:"protocol,omitempty"`
	Service  string `json:"service,omitempty"`
}

type Collector struct {
	apiKey         string
	apiSecret      string
	cloudAccountID uint64
	reporter       collector.EventReporter
	httpClient     *http.Client
	baseURL        string
}

func NewCollector(apiKey, apiSecret string, cloudAccountID uint64) *Collector {
	return &Collector{
		apiKey:         apiKey,
		apiSecret:      apiSecret,
		cloudAccountID: cloudAccountID,
		httpClient:     &http.Client{Timeout: 15 * time.Second},
		baseURL:        baseURLProduction, // Default to production
	}
}

// SetEnvironment configures the API environment (production or OTE test)
func (c *Collector) SetEnvironment(env string) {
	if env == "ote" || env == "test" {
		c.baseURL = baseURLOTE
		c.report("info", "Using GoDaddy OTE (test) environment", map[string]interface{}{
			"base_url": baseURLOTE,
		})
	} else {
		c.baseURL = baseURLProduction
	}
}

func (c *Collector) Provider() string {
	return "godaddy"
}

func (c *Collector) SetEventReporter(reporter collector.EventReporter) {
	c.reporter = reporter
}

func (c *Collector) report(eventType, message string, metadata map[string]interface{}) {
	if c.reporter != nil {
		c.reporter.Report(eventType, message, metadata)
	}
}

func (c *Collector) Collect(ctx context.Context) (*collector.AssetResult, error) {
	if err := c.Validate(); err != nil {
		return nil, err
	}

	start := time.Now()
	c.report("info", "Starting GoDaddy collection", nil)

	domains, err := c.fetchDomains(ctx)
	if err != nil {
		c.report("error", "Failed to fetch GoDaddy domains", map[string]interface{}{"error": err.Error()})
		return nil, err
	}
	c.report("info", "Fetched GoDaddy domains", map[string]interface{}{"count": len(domains)})

	var domainAssets []model.DomainAsset
	var assets []model.Asset

	for _, d := range domains {
		records, recErr := c.fetchRecords(ctx, d.Domain)
		if recErr != nil {
			c.report("warning", "Failed to fetch records for domain", map[string]interface{}{
				"domain": d.Domain,
				"error":  recErr.Error(),
			})
		}

		recordMaps := make([]map[string]interface{}, 0, len(records))
		for _, r := range records {
			rec := map[string]interface{}{
				"name": r.Name,
				"type": r.Type,
				"ttl":  r.TTL,
			}
			if r.Data != "" {
				rec["values"] = []string{r.Data}
				if strings.EqualFold(r.Type, "ALIAS") || strings.EqualFold(r.Type, "CNAME") {
					rec["alias_target"] = r.Data
				}
			}
			if r.Priority != 0 {
				rec["priority"] = r.Priority
			}
			if r.Port != 0 {
				rec["port"] = r.Port
			}
			if r.Weight != 0 {
				rec["weight"] = r.Weight
			}
			if r.Protocol != "" {
				rec["protocol"] = r.Protocol
			}
			if r.Service != "" {
				rec["service"] = r.Service
			}
			recordMaps = append(recordMaps, rec)
		}

		asset := model.Asset{
			CloudAccountID: &c.cloudAccountID,
			NativeID:       d.Domain,
			Name:           d.Domain,
			AssetType:      model.AssetTypeNetwork,
			Service:        "godaddy_dns",
			Status:         model.AssetStatusActive,
			Environment:    "",
			Region:         "global",
			Provider:       string(model.CloudProviderGoDaddy),
			Metadata:       map[string]interface{}{"provider": "godaddy"},
			FirstSeenAt:    d.Expires,
			LastSeenAt:     d.Expires,
		}
		assets = append(assets, asset)

		domainAsset := model.DomainAsset{
			Asset:        asset,
			DomainName:   d.Domain,
			DomainClass:  model.DomainClassAuthoritativeZone,
			Registrar:    "godaddy",
			ZoneID:       d.Domain,
			ZoneType:     "public",
			Status:       d.Status,
			NameServers:  d.NameServers,
			DNSRecords:   map[string]interface{}{"records": recordMaps, "fetched": time.Now()},
			LastSyncedAt: ptrTime(time.Now()),
			FirstSeenAt:  ptrTime(time.Now()),
			LastSeenAt:   ptrTime(time.Now()),
			ExpiresAt:    d.Expires,
		}
		domainAssets = append(domainAssets, domainAsset)
	}

	duration := time.Since(start)
	c.report("info", "Completed GoDaddy collection", map[string]interface{}{
		"domains":     len(domainAssets),
		"duration_ms": duration.Milliseconds(),
	})

	return &collector.AssetResult{
		Assets:       assets,
		DomainAssets: domainAssets,
		IPAssets:     []model.IPAsset{},
		Ports:        []model.IPPortIndex{},
		CollectedAt:  time.Now(),
	}, nil
}

// Validate ensures credentials are present and properly formatted.
func (c *Collector) Validate() error {
	if c.apiKey == "" || c.apiSecret == "" {
		return fmt.Errorf("missing GoDaddy API credentials (api_key and api_secret required)")
	}
	// GoDaddy API keys are typically 24+ characters
	if len(c.apiKey) < 20 || len(c.apiSecret) < 10 {
		return fmt.Errorf("GoDaddy API credentials appear invalid (too short). Get real credentials from https://developer.godaddy.com/keys")
	}
	return nil
}

func (c *Collector) authHeader() string {
	return fmt.Sprintf("sso-key %s:%s", c.apiKey, c.apiSecret)
}

func (c *Collector) fetchDomains(ctx context.Context) ([]domainResponse, error) {
	pageSize := 100
	offset := 0
	var all []domainResponse

	for {
		url := fmt.Sprintf("%s/domains?limit=%d&offset=%d&includes=nameServers", c.baseURL, pageSize, offset)
		resp, err := c.requestWithRetry(ctx, http.MethodGet, url)
		if err != nil {
			return nil, err
		}
		defer resp.Body.Close()
		if resp.StatusCode >= 300 {
			// Read error response body for better diagnostics
			var errorBody map[string]interface{}
			decodeErr := json.NewDecoder(resp.Body).Decode(&errorBody)

			if decodeErr == nil && errorBody != nil {
				c.report("error", "GoDaddy API authentication failed", map[string]interface{}{
					"status":     resp.StatusCode,
					"error_code": errorBody["code"],
					"error_msg":  errorBody["message"],
					"hint":       "Check API credentials at https://developer.godaddy.com/keys",
				})
				// Return detailed error with GoDaddy error code
				if code, ok := errorBody["code"].(string); ok {
					return nil, fmt.Errorf("godaddy API error: %s - %s", code, errorBody["message"])
				}
			} else {
				c.report("error", "GoDaddy API error (body unreadable)", map[string]interface{}{
					"status":       resp.StatusCode,
					"decode_error": fmt.Sprintf("%v", decodeErr),
				})
			}
			return nil, fmt.Errorf("godaddy domains request failed: %s", resp.Status)
		}

		var batch []domainResponse
		if err := json.NewDecoder(resp.Body).Decode(&batch); err != nil {
			return nil, err
		}
		all = append(all, batch...)

		c.report("info", "Fetched GoDaddy domain page", map[string]interface{}{
			"offset": offset,
			"count":  len(batch),
		})

		if len(batch) < pageSize {
			break
		}
		offset += pageSize
	}

	return all, nil
}

func (c *Collector) fetchRecords(ctx context.Context, domain string) ([]recordResponse, error) {
	resp, err := c.requestWithRetry(ctx, http.MethodGet, fmt.Sprintf("%s/domains/%s/records", c.baseURL, domain))
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	if resp.StatusCode >= 300 {
		return nil, fmt.Errorf("godaddy records request failed for %s: %s", domain, resp.Status)
	}

	var records []recordResponse
	if err := json.NewDecoder(resp.Body).Decode(&records); err != nil {
		return nil, err
	}
	return records, nil
}

// requestWithRetry wraps GoDaddy API calls with simple backoff for 429/5xx responses
// and emits per-attempt job events for visibility.
func (c *Collector) requestWithRetry(ctx context.Context, method, url string) (*http.Response, error) {
	const maxAttempts = 3
	var lastErr error

	for attempt := 1; attempt <= maxAttempts; attempt++ {
		req, err := http.NewRequestWithContext(ctx, method, url, nil)
		if err != nil {
			return nil, err
		}
		req.Header.Set("Authorization", c.authHeader())
		req.Header.Set("Accept", "application/json")
		// Some GoDaddy API operations may require additional headers
		// req.Header.Set("X-Shopper-Id", shopperID) // Add if needed

		start := time.Now()
		resp, err := c.httpClient.Do(req)
		duration := time.Since(start)
		meta := map[string]interface{}{
			"method":      method,
			"url":         url,
			"attempt":     attempt,
			"duration_ms": duration.Milliseconds(),
		}

		if err != nil {
			meta["error"] = err.Error()
			c.report("warning", "GoDaddy request error", meta)
			lastErr = err
		} else {
			meta["status"] = resp.StatusCode
			if resp.StatusCode == http.StatusTooManyRequests || resp.StatusCode >= 500 {
				if retryAfter := resp.Header.Get("Retry-After"); retryAfter != "" {
					meta["retry_after"] = retryAfter
				}
				c.report("warning", "GoDaddy request throttled", meta)
				lastErr = fmt.Errorf("godaddy %s failed: %s", method, resp.Status)
				resp.Body.Close()
			} else {
				c.report("info", "GoDaddy request success", meta)
				return resp, nil
			}
		}

		if attempt == maxAttempts {
			break
		}

		// Compute backoff with optional Retry-After header seconds override.
		delay := time.Duration(attempt*attempt) * time.Second
		if resp != nil && resp.Header != nil {
			if retryAfter := resp.Header.Get("Retry-After"); retryAfter != "" {
				if secs, err := strconv.Atoi(retryAfter); err == nil {
					delay = time.Duration(secs) * time.Second
				}
			}
		}

		select {
		case <-time.After(delay):
		case <-ctx.Done():
			return nil, ctx.Err()
		}
	}

	return nil, lastErr
}

func ptrTime(t time.Time) *time.Time {
	return &t
}
