package desec

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

// TokenPolicy represents a policy applied to a token.
type TokenPolicy struct {
	ID              string  `json:"id,omitempty"`
	Domain          *string `json:"domain"`
	SubName         *string `json:"subname"`
	Type            *string `json:"type"`
	WritePermission bool    `json:"perm_write,omitempty"`
}

// TokenPoliciesService handles communication with the token policy related methods of the deSEC API.
//
// https://desec.readthedocs.io/en/latest/auth/tokens.html
type TokenPoliciesService struct {
	client *Client
}

// Deprecated: use [TokenPoliciesService.GetAll] instead.
func (s *TokenPoliciesService) Get(ctx context.Context, tokenID string) ([]TokenPolicy, error) {
	return s.GetAll(ctx, tokenID)
}

// GetOne retrieves a specific token rrset policy.
// https://desec.readthedocs.io/en/latest/auth/tokens.html#token-policy-management
func (s *TokenPoliciesService) GetOne(ctx context.Context, tokenID, policyID string) (*TokenPolicy, error) {
	endpoint, err := s.client.createEndpoint("auth", "tokens", tokenID, "policies", "rrsets", policyID)
	if err != nil {
		return nil, fmt.Errorf("failed to create endpoint: %w", err)
	}

	req, err := s.client.newRequest(ctx, http.MethodGet, endpoint, nil)
	if err != nil {
		return nil, err
	}

	resp, err := s.client.httpClient.Do(req)
	if err != nil {
		return nil, fmt.Errorf("failed to call API: %w", err)
	}

	defer func() { _ = resp.Body.Close() }()

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

	policy := &TokenPolicy{}

	err = handleResponse(resp, policy)
	if err != nil {
		return nil, err
	}

	return policy, nil
}

// GetAll retrieves all rrset policies for a token.
// https://desec.readthedocs.io/en/latest/auth/tokens.html#token-policy-management
func (s *TokenPoliciesService) GetAll(ctx context.Context, tokenID string) ([]TokenPolicy, error) {
	endpoint, err := s.client.createEndpoint("auth", "tokens", tokenID, "policies", "rrsets")
	if err != nil {
		return nil, fmt.Errorf("failed to create endpoint: %w", err)
	}

	req, err := s.client.newRequest(ctx, http.MethodGet, endpoint, nil)
	if err != nil {
		return nil, err
	}

	resp, err := s.client.httpClient.Do(req)
	if err != nil {
		return nil, fmt.Errorf("failed to call API: %w", err)
	}

	defer func() { _ = resp.Body.Close() }()

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

	var policies []TokenPolicy

	err = handleResponse(resp, &policies)
	if err != nil {
		return nil, err
	}

	return policies, nil
}

// Create creates token policy.
// https://desec.readthedocs.io/en/latest/auth/tokens.html#create-additional-tokens
func (s *TokenPoliciesService) Create(ctx context.Context, tokenID string, policy TokenPolicy) (*TokenPolicy, error) {
	endpoint, err := s.client.createEndpoint("auth", "tokens", tokenID, "policies", "rrsets")
	if err != nil {
		return nil, fmt.Errorf("failed to create endpoint: %w", err)
	}

	req, err := s.client.newRequest(ctx, http.MethodPost, endpoint, policy)
	if err != nil {
		return nil, err
	}

	resp, err := s.client.httpClient.Do(req)
	if err != nil {
		return nil, fmt.Errorf("failed to call API: %w", err)
	}

	defer func() { _ = resp.Body.Close() }()

	if resp.StatusCode != http.StatusCreated {
		return nil, handleError(resp)
	}

	var tokenPolicy TokenPolicy

	err = handleResponse(resp, &tokenPolicy)
	if err != nil {
		return nil, err
	}

	return &tokenPolicy, nil
}

// Update a token policy
// https://desec.readthedocs.io/en/latest/auth/tokens.html#token-policy-management
func (s *TokenPoliciesService) Update(ctx context.Context, tokenID, policyID string, policy TokenPolicy) (*TokenPolicy, error) {
	endpoint, err := s.client.createEndpoint("auth", "tokens", tokenID, "policies", "rrsets", policyID)
	if err != nil {
		return nil, fmt.Errorf("failed to create endpoint: %w", err)
	}

	// Copy values, including only fields that can be modified
	req, err := s.client.newRequest(ctx, http.MethodPatch, endpoint, TokenPolicy{
		Domain:          policy.Domain,
		SubName:         policy.SubName,
		Type:            policy.Type,
		WritePermission: policy.WritePermission,
	})
	if err != nil {
		return nil, err
	}

	resp, err := s.client.httpClient.Do(req)
	if err != nil {
		return nil, fmt.Errorf("failed to call API: %w", err)
	}

	defer func() { _ = resp.Body.Close() }()

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

	result := &TokenPolicy{}

	err = handleResponse(resp, result)
	if err != nil {
		return nil, err
	}

	return result, nil
}

// Delete deletes a token rrset's policy.
// https://desec.readthedocs.io/en/latest/auth/tokens.html#token-policy-management
func (s *TokenPoliciesService) Delete(ctx context.Context, tokenID, policyID string) error {
	endpoint, err := s.client.createEndpoint("auth", "tokens", tokenID, "policies", "rrsets", policyID)
	if err != nil {
		return fmt.Errorf("failed to create endpoint: %w", err)
	}

	req, err := s.client.newRequest(ctx, http.MethodDelete, endpoint, nil)
	if err != nil {
		return err
	}

	resp, err := s.client.httpClient.Do(req)
	if err != nil {
		return fmt.Errorf("failed to call API: %w", err)
	}

	defer func() { _ = resp.Body.Close() }()

	if resp.StatusCode != http.StatusNoContent {
		return handleError(resp)
	}

	return nil
}
