package client

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

	"github.com/sirupsen/logrus"

	"github.com/titansys/appy-builder/internal/config"
	"github.com/titansys/appy-builder/internal/models"
)

// AppyClient handles communication with the Appy server
type AppyClient struct {
	config     *config.Config
	httpClient *http.Client
	logger     *logrus.Logger
}

// NewAppyClient creates a new Appy API client
func NewAppyClient(cfg *config.Config, logger *logrus.Logger) *AppyClient {
	return &AppyClient{
		config: cfg,
		logger: logger,
		httpClient: &http.Client{
			Timeout: time.Duration(cfg.SiteTimeout) * time.Second,
		},
	}
}

// GetPendingBuilds fetches pending build requests from Appy server
func (c *AppyClient) GetPendingBuilds() ([]models.BuildRequest, error) {
	url := fmt.Sprintf("%s/api/v1/builds/pending/%s", c.config.SiteURL, c.config.Key)

	c.logger.WithFields(logrus.Fields{
		"url": url,
	}).Debug("Fetching pending builds from Appy")

	var response models.PendingBuildsResponse
	if err := c.doRequest("GET", url, nil, &response); err != nil {
		return nil, fmt.Errorf("failed to get pending builds: %w", err)
	}

	c.logger.WithFields(logrus.Fields{
		"count": len(response.Builds),
	}).Info("Received pending builds from Appy")

	return response.Builds, nil
}

// UpdateBuildStatus updates the build status on the Appy server
func (c *AppyClient) UpdateBuildStatus(buildID string, status models.StatusUpdateRequest) error {
	url := fmt.Sprintf("%s/api/v1/builds/%s/status/%s", c.config.SiteURL, buildID, c.config.Key)

	c.logger.WithFields(logrus.Fields{
		"build_id": buildID,
		"status":   status.Status,
	}).Info("Updating build status")

	if err := c.doRequest("POST", url, status, nil); err != nil {
		c.logger.WithError(err).WithFields(logrus.Fields{
			"build_id": buildID,
		}).Error("Failed to update build status")
		return fmt.Errorf("failed to update build status for %s: %w", buildID, err)
	}

	c.logger.WithFields(logrus.Fields{
		"build_id": buildID,
		"status":   status.Status,
	}).Info("Build status updated successfully")

	return nil
}

// DownloadKeystore downloads a keystore file from Appy
func (c *AppyClient) DownloadKeystore(keystoreID string) ([]byte, error) {
	url := fmt.Sprintf("%s/api/v1/keystores/%s/download/%s", c.config.SiteURL, keystoreID, c.config.Key)

	// Create request
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return nil, fmt.Errorf("failed to create request: %w", err)
	}

	// Add accept header
	req.Header.Set("Accept", "application/octet-stream")

	// Execute request
	resp, err := c.httpClient.Do(req)
	if err != nil {
		return nil, fmt.Errorf("failed to download keystore: %w", err)
	}
	defer resp.Body.Close()

	// Check status code
	if resp.StatusCode != http.StatusOK {
		bodyBytes, _ := io.ReadAll(resp.Body)
		return nil, fmt.Errorf("keystore download failed with status %d: %s", resp.StatusCode, string(bodyBytes))
	}

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

	return data, nil
}

// GetPendingBuildsDynamic fetches pending builds from a specific Appy instance
func (c *AppyClient) GetPendingBuildsDynamic(siteURL, authKey string) ([]models.BuildRequest, error) {
	url := fmt.Sprintf("%s/api/v1/builds/pending/%s", siteURL, authKey)

	c.logger.WithFields(logrus.Fields{
		"url": url,
	}).Debug("Fetching pending builds from Appy (dynamic)")

	var response models.PendingBuildsResponse
	if err := c.doRequest("GET", url, nil, &response); err != nil {
		return nil, fmt.Errorf("failed to get pending builds: %w", err)
	}

	c.logger.WithFields(logrus.Fields{
		"count":    len(response.Builds),
		"site_url": siteURL,
	}).Info("Received pending builds from Appy")

	return response.Builds, nil
}

// UpdateBuildStatusDynamic updates build status on a specific Appy instance
func (c *AppyClient) UpdateBuildStatusDynamic(buildID string, status models.StatusUpdateRequest, siteURL, authKey string) error {
	url := fmt.Sprintf("%s/api/v1/builds/%s/status/%s", siteURL, buildID, authKey)

	c.logger.WithFields(logrus.Fields{
		"build_id": buildID,
		"status":   status.Status,
		"site_url": siteURL,
	}).Info("Updating build status (dynamic)")

	if err := c.doRequest("POST", url, status, nil); err != nil {
		c.logger.WithError(err).WithFields(logrus.Fields{
			"build_id": buildID,
			"site_url": siteURL,
		}).Error("Failed to update build status")
		return fmt.Errorf("failed to update build status for %s: %w", buildID, err)
	}

	c.logger.WithFields(logrus.Fields{
		"build_id": buildID,
		"status":   status.Status,
		"site_url": siteURL,
	}).Info("Build status updated successfully (dynamic)")

	return nil
}

// DownloadKeystoreDynamic downloads keystore from a specific Appy instance
func (c *AppyClient) DownloadKeystoreDynamic(keystoreID, siteURL, authKey string) ([]byte, error) {
	url := fmt.Sprintf("%s/api/v1/keystores/%s/download/%s", siteURL, keystoreID, authKey)

	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return nil, fmt.Errorf("failed to create request: %w", err)
	}

	req.Header.Set("Accept", "application/octet-stream")

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

	if resp.StatusCode != http.StatusOK {
		bodyBytes, _ := io.ReadAll(resp.Body)
		return nil, fmt.Errorf("keystore download failed with status %d: %s", resp.StatusCode, string(bodyBytes))
	}

	return io.ReadAll(resp.Body)
}

// doRequest performs an HTTP request with retry logic
func (c *AppyClient) doRequest(method, url string, body interface{}, response interface{}) error {
	var lastErr error

	for attempt := 0; attempt <= c.config.SiteRetry; attempt++ {
		if attempt > 0 {
			// Wait before retry (exponential backoff)
			time.Sleep(time.Duration(attempt) * 2 * time.Second)
		}

		// Prepare request body
		var reqBody io.Reader
		if body != nil {
			jsonData, err := json.Marshal(body)
			if err != nil {
				return fmt.Errorf("failed to marshal request body: %w", err)
			}
			reqBody = bytes.NewReader(jsonData)
		}

		// Create request
		req, err := http.NewRequest(method, url, reqBody)
		if err != nil {
			lastErr = err
			continue
		}

		if body != nil {
			req.Header.Set("Content-Type", "application/json")
		}
		req.Header.Set("Accept", "application/json")

		// Execute request
		resp, err := c.httpClient.Do(req)
		if err != nil {
			lastErr = err
			continue
		}
		defer resp.Body.Close()

		// Read response body
		respBody, err := io.ReadAll(resp.Body)
		if err != nil {
			lastErr = err
			continue
		}

		// Check status code
		if resp.StatusCode < 200 || resp.StatusCode >= 300 {
			lastErr = fmt.Errorf("HTTP %d from %s: %s", resp.StatusCode, url, string(respBody))
			c.logger.WithFields(logrus.Fields{
				"method":      method,
				"url":         url,
				"status_code": resp.StatusCode,
			}).Error("Request failed")
			continue
		}

		// Parse response if needed
		if response != nil && len(respBody) > 0 {
			if err := json.Unmarshal(respBody, response); err != nil {
				lastErr = fmt.Errorf("failed to parse response: %w", err)
				continue
			}
		}

		// Success
		return nil
	}

	return fmt.Errorf("request failed after %d attempts: %w", c.config.SiteRetry+1, lastErr)
}
