package appsec

import (
	"context"
	"fmt"
	"net/http"

	"github.com/akamai/AkamaiOPEN-edgegrid-golang/v13/pkg/session"
	validation "github.com/go-ozzo/ozzo-validation/v4"
)

type (
	// The MalwarePolicy interface supports creating, retrieving, updating and removing malware protection policies.
	MalwarePolicy interface {
		// CreateMalwarePolicy creates a new malware protection policy.
		//
		// See: https://techdocs.akamai.com/application-security/reference/post-malware-policies
		CreateMalwarePolicy(ctx context.Context, params CreateMalwarePolicyRequest) (*MalwarePolicyResponse, error)

		// GetMalwarePolicy retrieves an existing malware protection policy.
		//
		// See: https://techdocs.akamai.com/application-security/reference/get-malware-policy
		GetMalwarePolicy(ctx context.Context, params GetMalwarePolicyRequest) (*MalwarePolicyResponse, error)

		// GetMalwarePolicies retrieves the existing malware protection policies for a configuration.
		//
		// See: https://techdocs.akamai.com/application-security/reference/get-malware-policies
		GetMalwarePolicies(ctx context.Context, params GetMalwarePoliciesRequest) (*MalwarePoliciesResponse, error)

		// UpdateMalwarePolicy modifies an existing malware protection policy.
		//
		// See: https://techdocs.akamai.com/application-security/reference/put-malware-policy
		UpdateMalwarePolicy(ctx context.Context, params UpdateMalwarePolicyRequest) (*MalwarePolicyResponse, error)

		// RemoveMalwarePolicy removes a malware protection policy.
		//
		// See: https://techdocs.akamai.com/application-security/reference/delete-malware-policy
		RemoveMalwarePolicy(ctx context.Context, params RemoveMalwarePolicyRequest) error
	}

	// MalwarePolicyBody describes a malware policy.
	MalwarePolicyBody struct {
		MalwarePolicyID int           `json:"id"`
		Name            string        `json:"name"`
		Description     string        `json:"description,omitempty"`
		Hostnames       []string      `json:"hostnames"`
		Paths           []string      `json:"paths"`
		ContentTypes    []ContentType `json:"contentTypes,omitempty"`
		LogFilename     bool          `json:"logFilename,omitempty"`
		AllowListID     string        `json:"allowListId,omitempty"`
		BlockListID     string        `json:"blockListId,omitempty"`
	}

	// ContentType describes a content type and its optional encoded content attributes.
	ContentType struct {
		Name                     string                    `json:"name"`
		EncodedContentAttributes []EncodedContentAttribute `json:"encodedContentAttributes,omitempty"`
	}

	// EncodedContentAttribute describes a JSON path to a property with encoded content.
	EncodedContentAttribute struct {
		Path     string   `json:"path"`
		Encoding []string `json:"encoding"`
	}

	// CreateMalwarePolicyRequest is used to create a malware policy.
	CreateMalwarePolicyRequest struct {
		ConfigID      int
		ConfigVersion int
		Policy        *MalwarePolicyBody
	}

	// MalwarePolicyResponse is returned from a call to CreateMalwarePolicy
	MalwarePolicyResponse struct {
		MalwarePolicyID int           `json:"id"`
		Name            string        `json:"name"`
		Description     string        `json:"description,omitempty"`
		Hostnames       []string      `json:"hostnames"`
		Paths           []string      `json:"paths"`
		ContentTypes    []ContentType `json:"contentTypes,omitempty"`
		LogFilename     bool          `json:"logFilename,omitempty"`
		AllowListID     string        `json:"allowListId,omitempty"`
		BlockListID     string        `json:"blockListId,omitempty"`
	}

	// GetMalwarePolicyRequest is used to retrieve an existing malware policy.
	GetMalwarePolicyRequest struct {
		ConfigID        int
		ConfigVersion   int
		MalwarePolicyID int
	}

	// GetMalwarePoliciesRequest is used to retrieve the malware policies for a configuration.
	GetMalwarePoliciesRequest struct {
		ConfigID        int
		ConfigVersion   int
		MalwarePolicyID int
	}

	// MalwarePoliciesResponse is returned from a call to GetMalwarePolicies.
	MalwarePoliciesResponse struct {
		MalwarePolicies []MalwarePolicyResponse
	}

	// UpdateMalwarePolicyRequest is used to update an existing malware policy.
	UpdateMalwarePolicyRequest struct {
		ConfigID        int
		ConfigVersion   int
		MalwarePolicyID int
		Policy          *MalwarePolicyBody
	}

	// RemoveMalwarePolicyRequest is used to remove a malware policy.
	RemoveMalwarePolicyRequest struct {
		ConfigID        int
		ConfigVersion   int
		MalwarePolicyID int
	}
)

// Validate validates a GetMalwarePolicyRequest.
func (v GetMalwarePolicyRequest) Validate() error {
	return validation.Errors{
		"ConfigID":        validation.Validate(v.ConfigID, validation.Required),
		"ConfigVersion":   validation.Validate(v.ConfigVersion, validation.Required),
		"MalwarePolicyID": validation.Validate(v.MalwarePolicyID, validation.Required),
	}.Filter()
}

// Validate validates a GetMalwarePolicysRequest.
func (v GetMalwarePoliciesRequest) Validate() error {
	return validation.Errors{
		"ConfigID":      validation.Validate(v.ConfigID, validation.Required),
		"ConfigVersion": validation.Validate(v.ConfigVersion, validation.Required),
	}.Filter()
}

// Validate validates a CreateMalwarePolicyRequest.
func (v CreateMalwarePolicyRequest) Validate() error {
	return validation.Errors{
		"ConfigID":      validation.Validate(v.ConfigID, validation.Required),
		"ConfigVersion": validation.Validate(v.ConfigVersion, validation.Required),
		"Policy":        validation.Validate(v.Policy, validation.Required),
	}.Filter()
}

// Validate validates an UpdateMalwarePolicyRequest.
func (v UpdateMalwarePolicyRequest) Validate() error {
	return validation.Errors{
		"ConfigID":        validation.Validate(v.ConfigID, validation.Required),
		"ConfigVersion":   validation.Validate(v.ConfigVersion, validation.Required),
		"MalwarePolicyID": validation.Validate(v.MalwarePolicyID, validation.Required),
		"Policy":          validation.Validate(v.Policy, validation.Required),
	}.Filter()
}

// Validate validates a RemoveMalwarePolicyRequest.
func (v RemoveMalwarePolicyRequest) Validate() error {
	return validation.Errors{
		"ConfigID":        validation.Validate(v.ConfigID, validation.Required),
		"ConfigVersion":   validation.Validate(v.ConfigVersion, validation.Required),
		"MalwarePolicyID": validation.Validate(v.MalwarePolicyID, validation.Required),
	}.Filter()
}

func (p *appsec) CreateMalwarePolicy(ctx context.Context, params CreateMalwarePolicyRequest) (*MalwarePolicyResponse, error) {
	logger := p.Log(ctx)
	logger.Debug("CreateMalwarePolicy")

	if err := params.Validate(); err != nil {
		return nil, fmt.Errorf("%w: %s", ErrStructValidation, err.Error())
	}

	uri := fmt.Sprintf(
		"/appsec/v1/configs/%d/versions/%d/malware-policies",
		params.ConfigID,
		params.ConfigVersion,
	)

	req, err := http.NewRequestWithContext(ctx, http.MethodPost, uri, nil)
	if err != nil {
		return nil, fmt.Errorf("failed to create CreateMalwarePolicy request: %w", err)
	}

	var result MalwarePolicyResponse
	resp, err := p.Exec(req, &result, params.Policy)
	if err != nil {
		return nil, fmt.Errorf("create malware policy request failed: %w", err)
	}
	defer session.CloseResponseBody(resp)

	if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated {
		return nil, p.Error(resp)
	}

	return &result, nil
}

func (p *appsec) GetMalwarePolicy(ctx context.Context, params GetMalwarePolicyRequest) (*MalwarePolicyResponse, error) {
	logger := p.Log(ctx)
	logger.Debug("GetMalwarePolicy")

	if err := params.Validate(); err != nil {
		return nil, fmt.Errorf("%w: %s", ErrStructValidation, err.Error())
	}

	uri := fmt.Sprintf(
		"/appsec/v1/configs/%d/versions/%d/malware-policies/%d",
		params.ConfigID,
		params.ConfigVersion,
		params.MalwarePolicyID)

	req, err := http.NewRequestWithContext(ctx, http.MethodGet, uri, nil)
	if err != nil {
		return nil, fmt.Errorf("failed to create GetMalwarePolicy request: %w", err)
	}

	var result MalwarePolicyResponse
	resp, err := p.Exec(req, &result)
	if err != nil {
		return nil, fmt.Errorf("get malware policy request failed: %w", err)
	}
	defer session.CloseResponseBody(resp)

	if resp.StatusCode != http.StatusOK {
		return nil, p.Error(resp)
	}

	return &result, nil
}

func (p *appsec) GetMalwarePolicies(ctx context.Context, params GetMalwarePoliciesRequest) (*MalwarePoliciesResponse, error) {
	logger := p.Log(ctx)
	logger.Debug("GetMalwarePolicies")

	if err := params.Validate(); err != nil {
		return nil, fmt.Errorf("%w: %s", ErrStructValidation, err.Error())
	}

	uri := fmt.Sprintf(
		"/appsec/v1/configs/%d/versions/%d/malware-policies",
		params.ConfigID,
		params.ConfigVersion,
	)

	req, err := http.NewRequestWithContext(ctx, http.MethodGet, uri, nil)
	if err != nil {
		return nil, fmt.Errorf("failed to create GetMalwarePolicies request: %w", err)
	}

	var result MalwarePoliciesResponse
	resp, err := p.Exec(req, &result)
	if err != nil {
		return nil, fmt.Errorf("get malware policies request failed: %w", err)
	}
	defer session.CloseResponseBody(resp)

	if resp.StatusCode != http.StatusOK {
		return nil, p.Error(resp)
	}

	if params.MalwarePolicyID != 0 {
		var filteredResult MalwarePoliciesResponse
		for _, val := range result.MalwarePolicies {
			if val.MalwarePolicyID == params.MalwarePolicyID {
				filteredResult.MalwarePolicies = append(filteredResult.MalwarePolicies, val)
			}
		}
		return &filteredResult, nil
	}

	return &result, nil
}

func (p *appsec) UpdateMalwarePolicy(ctx context.Context, params UpdateMalwarePolicyRequest) (*MalwarePolicyResponse, error) {
	logger := p.Log(ctx)
	logger.Debug("UpdateMalwarePolicy")

	if err := params.Validate(); err != nil {
		return nil, fmt.Errorf("%w: %s", ErrStructValidation, err.Error())
	}

	uri := fmt.Sprintf(
		"/appsec/v1/configs/%d/versions/%d/malware-policies/%d",
		params.ConfigID,
		params.ConfigVersion,
		params.MalwarePolicyID,
	)

	req, err := http.NewRequestWithContext(ctx, http.MethodPut, uri, nil)
	if err != nil {
		return nil, fmt.Errorf("failed to create UpdateMalwarePolicy request: %w", err)
	}
	req.Header.Set("Content-Type", "application/json")

	var result MalwarePolicyResponse
	resp, err := p.Exec(req, &result, params.Policy)
	if err != nil {
		return nil, fmt.Errorf("update malware policy request failed: %w", err)
	}
	defer session.CloseResponseBody(resp)

	if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated {
		return nil, p.Error(resp)
	}

	return &result, nil
}

func (p *appsec) RemoveMalwarePolicy(ctx context.Context, params RemoveMalwarePolicyRequest) error {
	logger := p.Log(ctx)
	logger.Debug("RemoveMalwarePolicy")

	if err := params.Validate(); err != nil {
		return fmt.Errorf("%w: %s", ErrStructValidation, err.Error())
	}

	uri := fmt.Sprintf("/appsec/v1/configs/%d/versions/%d/malware-policies/%d", params.ConfigID, params.ConfigVersion, params.MalwarePolicyID)
	req, err := http.NewRequestWithContext(ctx, http.MethodDelete, uri, nil)
	if err != nil {
		return fmt.Errorf("failed to create RemoveMalwarePolicy request: %w", err)
	}

	resp, err := p.Exec(req, nil)
	if err != nil {
		return fmt.Errorf("remove malware policy request failed: %w", err)
	}
	defer session.CloseResponseBody(resp)

	if resp.StatusCode != http.StatusNoContent && resp.StatusCode != http.StatusOK {
		return p.Error(resp)
	}

	return nil
}
