// ABOUTME: Application lifecycle management APIs for EdgeXR Master Controller
// ABOUTME: Provides typed methods for creating, querying, and deleting applications

package edgeconnect

import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net/http"

	"edp.buildth.ing/DevFW-CICD/edge-connect-client/internal/core/domain"
	sdkhttp "edp.buildth.ing/DevFW-CICD/edge-connect-client/internal/adapters/internal/http"
)

// Note: We now use domain.DomainError for structured error handling instead of simple errors

// CreateApp creates a new application in the specified region
// Maps to POST /auth/ctrl/CreateApp
func (c *Client) CreateApp(ctx context.Context, region string, app *domain.App) error {
	transport := c.getTransport()
	url := c.BaseURL + "/api/v1/auth/ctrl/CreateApp"

	apiApp := toAPIApp(app)
	input := &NewAppInput{
		Region: region,
		App:    *apiApp,
	}

	resp, err := transport.Call(ctx, "POST", url, input)
	if err != nil {
		return fmt.Errorf("CreateApp failed: %w", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode >= 400 {
		return c.handleErrorResponse(resp, "CreateApp")
	}

	c.logf("CreateApp: %s/%s version %s created successfully",
		input.App.Key.Organization, input.App.Key.Name, input.App.Key.Version)

	return nil
}

// ShowApp retrieves a single application by key and region
// Maps to POST /auth/ctrl/ShowApp
func (c *Client) ShowApp(ctx context.Context, region string, appKey domain.AppKey) (*domain.App, error) {
	transport := c.getTransport()
	url := c.BaseURL + "/api/v1/auth/ctrl/ShowApp"

	apiAppKey := toAPIAppKey(appKey)
	filter := AppFilter{
		App:    App{Key: *apiAppKey},
		Region: region,
	}

	resp, err := transport.Call(ctx, "POST", url, filter)
	if err != nil {
		return nil, fmt.Errorf("ShowApp failed: %w", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode == http.StatusNotFound {
		return nil, domain.NewAppError(domain.ErrResourceNotFound, "ShowApp", appKey, region, 
			"application not found")
	}

	if resp.StatusCode >= 400 {
		return nil, c.handleErrorResponse(resp, "ShowApp")
	}

	// Parse streaming JSON response
	var apps []App
	if err := c.parseStreamingResponse(resp, &apps); err != nil {
		return nil, fmt.Errorf("ShowApp failed to parse response: %w", err)
	}

	if len(apps) == 0 {
		return nil, domain.NewAppError(domain.ErrResourceNotFound, "ShowApp", appKey, region, 
			"application not found")
	}

	domainApp := toDomainApp(&apps[0])
	return &domainApp, nil
}

// ShowApps retrieves all applications matching the filter criteria
// Maps to POST /auth/ctrl/ShowApp
func (c *Client) ShowApps(ctx context.Context, region string, appKey domain.AppKey) ([]domain.App, error) {
	transport := c.getTransport()
	url := c.BaseURL + "/api/v1/auth/ctrl/ShowApp"

	apiAppKey := toAPIAppKey(appKey)
	filter := AppFilter{
		App:    App{Key: *apiAppKey},
		Region: region,
	}

	resp, err := transport.Call(ctx, "POST", url, filter)
	if err != nil {
		return nil, fmt.Errorf("ShowApps failed: %w", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode >= 400 && resp.StatusCode != http.StatusNotFound {
		return nil, c.handleErrorResponse(resp, "ShowApps")
	}

	var apps []App
	if resp.StatusCode == http.StatusNotFound {
		return []domain.App{}, nil // Return empty slice for not found
	}

	if err := c.parseStreamingResponse(resp, &apps); err != nil {
		return nil, fmt.Errorf("ShowApps failed to parse response: %w", err)
	}

	c.logf("ShowApps: found %d apps matching criteria", len(apps))
	
	domainApps := make([]domain.App, len(apps))
	for i := range apps {
		domainApps[i] = toDomainApp(&apps[i])
	}

	return domainApps, nil
}

// UpdateApp updates the definition of an application
// Maps to POST /auth/ctrl/UpdateApp
func (c *Client) UpdateApp(ctx context.Context, region string, app *domain.App) error {
	transport := c.getTransport()
	url := c.BaseURL + "/api/v1/auth/ctrl/UpdateApp"

	apiApp := toAPIApp(app)
	input := &UpdateAppInput{
		Region: region,
		App:    *apiApp,
	}

	resp, err := transport.Call(ctx, "POST", url, input)
	if err != nil {
		return fmt.Errorf("UpdateApp failed: %w", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode >= 400 {
		return c.handleErrorResponse(resp, "UpdateApp")
	}

	c.logf("UpdateApp: %s/%s version %s updated successfully",
		input.App.Key.Organization, input.App.Key.Name, input.App.Key.Version)

	return nil
}

// DeleteApp removes an application from the specified region
// Maps to POST /auth/ctrl/DeleteApp
func (c *Client) DeleteApp(ctx context.Context, region string, appKey domain.AppKey) error {
	transport := c.getTransport()
	url := c.BaseURL + "/api/v1/auth/ctrl/DeleteApp"

	apiAppKey := toAPIAppKey(appKey)
	filter := AppFilter{
		App:    App{Key: *apiAppKey},
		Region: region,
	}

	resp, err := transport.Call(ctx, "POST", url, filter)
	if err != nil {
		return fmt.Errorf("DeleteApp failed: %w", err)
	}
	defer resp.Body.Close()

	// 404 is acceptable for delete operations (already deleted)
	if resp.StatusCode >= 400 && resp.StatusCode != http.StatusNotFound {
		return c.handleErrorResponse(resp, "DeleteApp")
	}

	c.logf("DeleteApp: %s/%s version %s deleted successfully",
		appKey.Organization, appKey.Name, appKey.Version)

	return nil
}

// parseStreamingResponse parses the EdgeXR streaming JSON response format
func (c *Client) parseStreamingResponse(resp *http.Response, result interface{}) error {
	var responses []Response[App]

	parseErr := sdkhttp.ParseJSONLines(resp.Body, func(line []byte) error {
		var response Response[App]
		if err := json.Unmarshal(line, &response); err != nil {
			return err
		}
		responses = append(responses, response)
		return nil
	})

	if parseErr != nil {
		return parseErr
	}

	// Extract data from responses
	var apps []App
	var messages []string

	for _, response := range responses {
		if response.HasData() {
			apps = append(apps, response.Data)
		}
		if response.IsMessage() {
			messages = append(messages, response.Data.GetMessage())
		}
	}

	// If we have error messages, return them
	if len(messages) > 0 {
		return &APIError{
			StatusCode: resp.StatusCode,
			Messages:   messages,
		}
	}

	// Set result based on type
	switch v := result.(type) {
	case *[]App:
		*v = apps
	default:
		return fmt.Errorf("unsupported result type: %T", result)
	}

	return nil
}

// getTransport creates an HTTP transport with current client settings
func (c *Client) getTransport() *sdkhttp.Transport {
	return sdkhttp.NewTransport(
		sdkhttp.RetryOptions{
			MaxRetries:               c.RetryOpts.MaxRetries,
			InitialDelay:             c.RetryOpts.InitialDelay,
			MaxDelay:                 c.RetryOpts.MaxDelay,
			Multiplier:               c.RetryOpts.Multiplier,
			RetryableHTTPStatusCodes: c.RetryOpts.RetryableHTTPStatusCodes,
		},
		c.AuthProvider,
		c.Logger,
	)
}

// handleErrorResponse creates an appropriate error from HTTP error response
func (c *Client) handleErrorResponse(resp *http.Response, operation string) error {

	messages := []string{
		fmt.Sprintf("%s failed with status %d", operation, resp.StatusCode),
	}

	bodyBytes := []byte{}

	if resp.Body != nil {
		defer resp.Body.Close()
		bodyBytes, _ = io.ReadAll(resp.Body)
		messages = append(messages, string(bodyBytes))
	}

	return &APIError{
		StatusCode: resp.StatusCode,
		Messages:   messages,
		Body:       bodyBytes,
	}
}

func toAPIApp(app *domain.App) *App {
	return &App{
		Key:                         *toAPIAppKey(app.Key),
		Deployment:                  app.Deployment,
		ImageType:                   app.ImageType,
		ImagePath:                   app.ImagePath,
		AllowServerless:             app.AllowServerless,
		DefaultFlavor:               toAPIFlavor(app.DefaultFlavor),
		ServerlessConfig:            app.ServerlessConfig,
		DeploymentGenerator:         app.DeploymentGenerator,
		DeploymentManifest:          app.DeploymentManifest,
		RequiredOutboundConnections: toAPISecurityRules(app.RequiredOutboundConnections),
		Fields:                      app.Fields,
	}
}

func toDomainApp(app *App) domain.App {
	return domain.App{
		Key:                         toDomainAppKey(app.Key),
		Deployment:                  app.Deployment,
		ImageType:                   app.ImageType,
		ImagePath:                   app.ImagePath,
		AllowServerless:             app.AllowServerless,
		DefaultFlavor:               toDomainFlavor(app.DefaultFlavor),
		ServerlessConfig:            app.ServerlessConfig,
		DeploymentGenerator:         app.DeploymentGenerator,
		DeploymentManifest:          app.DeploymentManifest,
		RequiredOutboundConnections: ToDomainSecurityRules(app.RequiredOutboundConnections),
		Fields:                      app.Fields,
	}
}

func toAPIAppKey(appKey domain.AppKey) *AppKey {
	return &AppKey{
		Organization: appKey.Organization,
		Name:         appKey.Name,
		Version:      appKey.Version,
	}
}

func toDomainAppKey(appKey AppKey) domain.AppKey {
	return domain.AppKey{
		Organization: appKey.Organization,
		Name:         appKey.Name,
		Version:      appKey.Version,
	}
}

func toAPIFlavor(flavor domain.Flavor) Flavor {
	return Flavor{Name: flavor.Name}
}

func toDomainFlavor(flavor Flavor) domain.Flavor {
	return domain.Flavor{Name: flavor.Name}
}

func toAPISecurityRules(rules []domain.SecurityRule) []SecurityRule {
	apiRules := make([]SecurityRule, len(rules))
	for i, r := range rules {
		apiRules[i] = SecurityRule{
			PortRangeMax: r.PortRangeMax,
			PortRangeMin: r.PortRangeMin,
			Protocol:     r.Protocol,
			RemoteCIDR:   r.RemoteCIDR,
		}
	}
	return apiRules
}

func ToDomainSecurityRules(rules []SecurityRule) []domain.SecurityRule {
	domainRules := make([]domain.SecurityRule, len(rules))
	for i, r := range rules {
		domainRules[i] = domain.SecurityRule{
			PortRangeMax: r.PortRangeMax,
			PortRangeMin: r.PortRangeMin,
			Protocol:     r.Protocol,
			RemoteCIDR:   r.RemoteCIDR,
		}
	}
	return domainRules
}