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

package v2

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"strings"
)

// CreateAppInstance creates a new application instance in the specified region
// Maps to POST /auth/ctrl/CreateAppInst
func (c *Client) CreateAppInstance(ctx context.Context, input *NewAppInstanceInput) error {

	transport := c.getTransport()
	url := c.BaseURL + "/api/v1/auth/ctrl/CreateAppInst"

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

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

	// Parse streaming JSON response
	if _, err = parseStreamingResponse[AppInstance](resp); err != nil {
		return fmt.Errorf("ShowAppInstance failed to parse response: %w", err)
	}

	c.logf("CreateAppInstance: %s/%s created successfully",
		input.AppInst.Key.Organization, input.AppInst.Key.Name)

	return nil
}

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

	filter := AppInstanceFilter{
		AppInstance: AppInstance{Key: appInstKey},
		Region:      region,
	}

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

	if resp.StatusCode == http.StatusNotFound {
		return AppInstance{}, fmt.Errorf("app instance %s/%s: %w",
			appInstKey.Organization, appInstKey.Name, ErrResourceNotFound)
	}

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

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

	if len(appInstances) == 0 {
		return AppInstance{}, fmt.Errorf("app instance %s/%s in region %s: %w",
			appInstKey.Organization, appInstKey.Name, region, ErrResourceNotFound)
	}

	return appInstances[0], nil
}

// ShowAppInstances retrieves all application instances matching the filter criteria
// Maps to POST /auth/ctrl/ShowAppInst
func (c *Client) ShowAppInstances(ctx context.Context, appInstKey AppInstanceKey, appKey AppKey, region string) ([]AppInstance, error) {
	transport := c.getTransport()
	url := c.BaseURL + "/api/v1/auth/ctrl/ShowAppInst"

	filter := AppInstanceFilter{
		AppInstance: AppInstance{Key: appInstKey, AppKey: appKey},
		Region:      region,
	}

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

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

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

	var appInstances []AppInstance
	if appInstances, err = parseStreamingResponse[AppInstance](resp); err != nil {
		return nil, fmt.Errorf("ShowAppInstances failed to parse response: %w", err)
	}

	c.logf("ShowAppInstances: found %d app instances matching criteria", len(appInstances))
	return appInstances, nil
}

// UpdateAppInstance updates an application instance and then refreshes it
// Maps to POST /auth/ctrl/UpdateAppInst
func (c *Client) UpdateAppInstance(ctx context.Context, input *UpdateAppInstanceInput) error {
	transport := c.getTransport()
	url := c.BaseURL + "/api/v1/auth/ctrl/UpdateAppInst"

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

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

	c.logf("UpdateAppInstance: %s/%s updated successfully",
		input.AppInst.Key.Organization, input.AppInst.Key.Name)

	return nil
}

// RefreshAppInstance refreshes an application instance's state
// Maps to POST /auth/ctrl/RefreshAppInst
func (c *Client) RefreshAppInstance(ctx context.Context, appInstKey AppInstanceKey, region string) error {
	transport := c.getTransport()
	url := c.BaseURL + "/api/v1/auth/ctrl/RefreshAppInst"

	filter := AppInstanceFilter{
		AppInstance: AppInstance{Key: appInstKey},
		Region:      region,
	}

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

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

	c.logf("RefreshAppInstance: %s/%s refreshed successfully",
		appInstKey.Organization, appInstKey.Name)

	return nil
}

// DeleteAppInstance removes an application instance
// Maps to POST /auth/ctrl/DeleteAppInst
func (c *Client) DeleteAppInstance(ctx context.Context, appInstKey AppInstanceKey, region string) error {
	transport := c.getTransport()
	url := c.BaseURL + "/api/v1/auth/ctrl/DeleteAppInst"

	input := DeleteAppInstanceInput{
		Region: region,
	}
	input.AppInst.Key = appInstKey

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

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

	c.logf("DeleteAppInstance: %s/%s deleted successfully",
		appInstKey.Organization, appInstKey.Name)

	return nil
}

// parseStreamingAppInstanceResponse parses the EdgeXR streaming JSON response format for app instances
func parseStreamingResponse[T Message](resp *http.Response) ([]T, error) {
	bodyBytes, err := io.ReadAll(resp.Body)
	if err != nil {
		return []T{}, fmt.Errorf("failed to read response body: %w", err)
	}

	// todo finish check the responses, test them, and make a unify result, probably need
	// to update the response parameter to the message type e.g. App or AppInst
	isV2, err := isV2Response(bodyBytes)
	if err != nil {
		return []T{}, fmt.Errorf("failed to parse streaming response: %w", err)
	}

	if isV2 {
		resultV2, err := parseStreamingResponseV2[T](resp.StatusCode, bodyBytes)
		if err != nil {
			return []T{}, err
		}
		return resultV2, nil
	}

	resultV1, err := parseStreamingResponseV1[T](resp.StatusCode, bodyBytes)
	if err != nil {
		return nil, err
	}

	if !resultV1.IsSuccessful() {
		return []T{}, resultV1.Error()
	}

	return resultV1.GetData(), nil
}

func parseStreamingResponseV1[T Message](statusCode int, bodyBytes []byte) (Responses[T], error) {
	// Fall back to streaming format (v1 API format)
	var responses Responses[T]
	responses.StatusCode = statusCode

	decoder := json.NewDecoder(bytes.NewReader(bodyBytes))
	for {
		var d Response[T]
		if err := decoder.Decode(&d); err != nil {
			if err.Error() == "EOF" {
				break
			}
			return Responses[T]{}, fmt.Errorf("error in parsing json object into Message: %w", err)
		}

		if d.Result.Message != "" && d.Result.Code != 0 {
			responses.StatusCode = d.Result.Code
		}

		if strings.Contains(d.Data.GetMessage(), "CreateError") {
			responses.Errors = append(responses.Errors, fmt.Errorf("server responded with: %s", "CreateError"))
		}

		if strings.Contains(d.Data.GetMessage(), "UpdateError") {
			responses.Errors = append(responses.Errors, fmt.Errorf("server responded with: %s", "UpdateError"))
		}

		if strings.Contains(d.Data.GetMessage(), "DeleteError") {
			responses.Errors = append(responses.Errors, fmt.Errorf("server responded with: %s", "DeleteError"))
		}

		responses.Responses = append(responses.Responses, d)
	}

	return responses, nil
}

func isV2Response(bodyBytes []byte) (bool, error) {
	if len(bodyBytes) == 0 {
		return false, fmt.Errorf("malformatted response body")
	}

	return bodyBytes[0] == '[', nil
}

func parseStreamingResponseV2[T Message](statusCode int, bodyBytes []byte) ([]T, error) {
	var result []T
	if err := json.Unmarshal(bodyBytes, &result); err != nil {
		return result, fmt.Errorf("failed to read response body: %w", err)
	}

	return result, nil
}
