package policy

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

	"github.com/acp/interceptor/internal/config"
	"github.com/acp/interceptor/internal/models"
)

// OPAClient calls OPA's Data API.
type OPAClient struct {
	baseURL      *url.URL
	decisionPath string
	httpClient   *http.Client
}

// NewOPAClient constructs an OPA HTTP client.
func NewOPAClient(cfg config.Config) (*OPAClient, error) {
	u, err := url.Parse(strings.TrimRight(cfg.OPABaseURL, "/"))
	if err != nil {
		return nil, fmt.Errorf("parse OPA base URL: %w", err)
	}
	return &OPAClient{
		baseURL:      u,
		decisionPath: strings.Trim(cfg.OPADecisionPath, "/"),
		httpClient: &http.Client{
			Timeout: cfg.OPAHTTPTimeout,
		},
	}, nil
}

type opaRequest struct {
	Input models.PolicyInput `json:"input"`
}

type opaResponse struct {
	Result json.RawMessage `json:"result"`
}

// Evaluate posts input to OPA and returns the raw result JSON (typically an object).
func (c *OPAClient) Evaluate(ctx context.Context, in models.PolicyInput) (json.RawMessage, error) {
	path := fmt.Sprintf("/v1/data/%s", c.decisionPath)
	full := c.baseURL.ResolveReference(&url.URL{Path: path, RawPath: path})

	payload := opaRequest{Input: in}
	body, err := json.Marshal(payload)
	if err != nil {
		return nil, fmt.Errorf("marshal OPA request: %w", err)
	}

	req, err := http.NewRequestWithContext(ctx, http.MethodPost, full.String(), bytes.NewReader(body))
	if err != nil {
		return nil, fmt.Errorf("build OPA request: %w", err)
	}
	req.Header.Set("Content-Type", "application/json")

	res, err := c.httpClient.Do(req)
	if err != nil {
		return nil, fmt.Errorf("opa request failed: %w", err)
	}
	defer res.Body.Close()

	b, err := io.ReadAll(io.LimitReader(res.Body, 1<<20))
	if err != nil {
		return nil, fmt.Errorf("read OPA response: %w", err)
	}
	if res.StatusCode < 200 || res.StatusCode >= 300 {
		return nil, fmt.Errorf("opa HTTP %d: %s", res.StatusCode, string(b))
	}

	var parsed opaResponse
	if err := json.Unmarshal(b, &parsed); err != nil {
		return nil, fmt.Errorf("decode OPA response: %w", err)
	}
	return parsed.Result, nil
}
