package ai_summarizer

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

	"tm/pkg/logger"
)

// Client is the HTTP client for the AI summarization on-demand API.
type Client struct {
	config     *Config
	httpClient *http.Client
	logger     logger.Logger
}

// NewClient creates a new AI summarizer HTTP client.
func NewClient(config *Config, log logger.Logger) (*Client, error) {
	if config == nil {
		config = DefaultConfig()
	}
	if err := config.ValidateAPI(); err != nil {
		return nil, fmt.Errorf("ai_summarizer: invalid config: %w", err)
	}

	return &Client{
		config: config,
		httpClient: &http.Client{
			Timeout: config.APITimeout,
		},
		logger: log,
	}, nil
}

// FetchSummaryOnDemand calls the external AI service POST /ai/summarize endpoint
// to generate a summary on demand. It retries on transient failures up to the
// configured number of attempts.
func (c *Client) FetchSummaryOnDemand(ctx context.Context, reqBody SummarizeRequest) (*SummarizeResponse, error) {
	jsonBody, err := json.Marshal(reqBody)
	if err != nil {
		return nil, fmt.Errorf("ai_summarizer: failed to marshal request body: %w", err)
	}

	url := c.config.APIBaseURL + "/ai/summarize"

	var lastErr error
	for attempt := 0; attempt <= c.config.APIRetryCount; attempt++ {
		if attempt > 0 {
			c.logger.Warn("Retrying AI summarize request", map[string]interface{}{
				"contract_folder_id":    reqBody.ContractFolderID,
				"notice_publication_id": reqBody.NoticePublicationID,
				"attempt":               attempt,
			})
			select {
			case <-ctx.Done():
				return nil, ctx.Err()
			case <-time.After(c.config.APIRetryDelay):
			}
		}

		resp, err := c.doPost(ctx, url, jsonBody)
		if err != nil {
			lastErr = err
			c.logger.Error("AI summarize request failed", map[string]interface{}{
				"contract_folder_id":    reqBody.ContractFolderID,
				"notice_publication_id": reqBody.NoticePublicationID,
				"attempt":               attempt,
				"error":                 err.Error(),
			})
			continue
		}

		return resp, nil
	}

	return nil, fmt.Errorf("ai_summarizer: all %d attempts failed, last error: %w", c.config.APIRetryCount+1, lastErr)
}

// FetchTranslationOnDemand calls the external AI service POST /ai/translate endpoint
// to generate title/description translation on demand.
func (c *Client) FetchTranslationOnDemand(ctx context.Context, reqBody TranslateRequest) (*TranslateResponse, error) {
	jsonBody, err := json.Marshal(reqBody)
	if err != nil {
		return nil, fmt.Errorf("ai_summarizer: failed to marshal translation request body: %w", err)
	}

	url := c.config.APIBaseURL + "/ai/translate"

	var lastErr error
	for attempt := 0; attempt <= c.config.APIRetryCount; attempt++ {
		if attempt > 0 {
			c.logger.Warn("Retrying AI translate request", map[string]interface{}{
				"notice_publication_id": reqBody.NoticePublicationID,
				"language":              reqBody.Language,
				"attempt":               attempt,
			})
			select {
			case <-ctx.Done():
				return nil, ctx.Err()
			case <-time.After(c.config.APIRetryDelay):
			}
		}

		resp, err := c.doTranslatePost(ctx, url, jsonBody, reqBody.RequestSource)
		if err != nil {
			lastErr = err
			c.logger.Error("AI translate request failed", map[string]interface{}{
				"notice_publication_id": reqBody.NoticePublicationID,
				"language":              reqBody.Language,
				"attempt":               attempt,
				"error":                 err.Error(),
			})
			continue
		}

		return resp, nil
	}

	return nil, fmt.Errorf("ai_summarizer: all %d translation attempts failed, last error: %w", c.config.APIRetryCount+1, lastErr)
}

// TriggerTranslateBatch calls POST /ai/translate/batch to enqueue background translation.
func (c *Client) TriggerTranslateBatch(ctx context.Context, tenders []TenderRef, languages []string) (*BatchAcceptedResponse, error) {
	reqBody := TranslateBatchRequest{
		Tenders:   tenders,
		Languages: languages,
	}
	return c.postBatchAccepted(ctx, "/ai/translate/batch", reqBody, "translate batch", map[string]interface{}{
		"tender_count": len(tenders),
		"languages":    languages,
	})
}

// TriggerSummarizeBatch calls POST /ai/summarize/batch to enqueue background summarization.
func (c *Client) TriggerSummarizeBatch(ctx context.Context, tenders []TenderRef) (*BatchAcceptedResponse, error) {
	reqBody := SummarizeBatchRequest{Tenders: tenders}
	return c.postBatchAccepted(ctx, "/ai/summarize/batch", reqBody, "summarize batch", map[string]interface{}{
		"tender_count": len(tenders),
	})
}

// ScrapeDocuments calls POST /scrape/documents for one tender (synchronous, cached).
func (c *Client) ScrapeDocuments(ctx context.Context, reqBody ScrapeDocumentsRequest) (*ScrapeDocumentsResponse, error) {
	jsonBody, err := json.Marshal(reqBody)
	if err != nil {
		return nil, fmt.Errorf("ai_summarizer: failed to marshal scrape request: %w", err)
	}

	url := c.config.APIBaseURL + "/scrape/documents"
	httpResp, bodyBytes, err := c.doRawPost(ctx, url, jsonBody)
	if err != nil {
		return nil, err
	}
	defer httpResp.Body.Close()

	if httpResp.StatusCode == http.StatusNotFound {
		return nil, fmt.Errorf("%w: status 404, body: %s", ErrObjectNotFound, string(bodyBytes))
	}
	if httpResp.StatusCode >= 400 {
		return nil, apiNonSuccessError(httpResp.StatusCode, bodyBytes)
	}

	var result ScrapeDocumentsResponse
	if err := json.Unmarshal(bodyBytes, &result); err != nil {
		return nil, fmt.Errorf("ai_summarizer: failed to decode scrape response: %w", err)
	}

	c.logger.Info("AI scrape documents request succeeded", map[string]interface{}{
		"contract_folder_id":    reqBody.ContractFolderID,
		"notice_publication_id": reqBody.NoticePublicationID,
		"files_downloaded":      result.FilesDownloaded,
	})

	return &result, nil
}

func apiNonSuccessError(statusCode int, bodyBytes []byte) error {
	return &APIStatusError{
		StatusCode: statusCode,
		Body:       string(bodyBytes),
	}
}

// ScrapeDocumentsBatch calls POST /scrape/documents/batch.
func (c *Client) ScrapeDocumentsBatch(ctx context.Context, tenders []TenderRef) (*BatchAcceptedResponse, error) {
	reqBody := ScrapeDocumentsBatchRequest{Tenders: tenders}
	return c.postBatchAccepted(ctx, "/scrape/documents/batch", reqBody, "scrape batch", map[string]interface{}{
		"tender_count": len(tenders),
	})
}

// GetScrapePortals calls GET /scrape/portals.
func (c *Client) GetScrapePortals(ctx context.Context) (*ScrapePortalsResponse, error) {
	url := c.config.APIBaseURL + "/scrape/portals"
	httpResp, bodyBytes, err := c.doRawGet(ctx, url)
	if err != nil {
		return nil, err
	}
	defer httpResp.Body.Close()

	if httpResp.StatusCode >= 400 {
		return nil, apiNonSuccessError(httpResp.StatusCode, bodyBytes)
	}

	var result ScrapePortalsResponse
	if len(bodyBytes) > 0 {
		if err := json.Unmarshal(bodyBytes, &result); err != nil {
			return nil, fmt.Errorf("ai_summarizer: failed to decode scrape portals response: %w", err)
		}
	}

	return &result, nil
}

// PipelineSync calls POST /pipeline/sync to pull the tender list from the backend.
func (c *Client) PipelineSync(ctx context.Context) (*PipelineSyncResponse, error) {
	url := c.config.APIBaseURL + "/pipeline/sync"
	httpResp, bodyBytes, err := c.doRawPost(ctx, url, nil)
	if err != nil {
		return nil, err
	}
	defer httpResp.Body.Close()

	if httpResp.StatusCode >= 400 {
		return nil, apiNonSuccessError(httpResp.StatusCode, bodyBytes)
	}

	var result PipelineSyncResponse
	if len(bodyBytes) > 0 {
		if err := json.Unmarshal(bodyBytes, &result); err != nil {
			return nil, fmt.Errorf("ai_summarizer: failed to decode pipeline sync response: %w", err)
		}
	}

	c.logger.Info("AI pipeline sync completed", map[string]interface{}{
		"stored": result.Stored,
	})

	return &result, nil
}

// PipelineRun calls POST /pipeline/run for one tender (scrape → translate → summarize).
func (c *Client) PipelineRun(ctx context.Context, reqBody PipelineRunRequest) (map[string]interface{}, error) {
	jsonBody, err := json.Marshal(reqBody)
	if err != nil {
		return nil, fmt.Errorf("ai_summarizer: failed to marshal pipeline run request: %w", err)
	}

	url := c.config.APIBaseURL + "/pipeline/run"
	httpResp, bodyBytes, err := c.doRawPost(ctx, url, jsonBody)
	if err != nil {
		return nil, err
	}
	defer httpResp.Body.Close()

	if httpResp.StatusCode == http.StatusNotFound {
		return nil, fmt.Errorf("%w: status 404, body: %s", ErrObjectNotFound, string(bodyBytes))
	}
	if httpResp.StatusCode >= 400 {
		return nil, apiNonSuccessError(httpResp.StatusCode, bodyBytes)
	}

	var result map[string]interface{}
	if len(bodyBytes) > 0 {
		if err := json.Unmarshal(bodyBytes, &result); err != nil {
			return nil, fmt.Errorf("ai_summarizer: failed to decode pipeline run response: %w", err)
		}
	}

	c.logger.Info("AI pipeline run completed", map[string]interface{}{
		"contract_folder_id":    reqBody.ContractFolderID,
		"notice_publication_id": reqBody.NoticePublicationID,
		"languages":             reqBody.Languages,
	})

	return result, nil
}

// PipelineRunBatch calls POST /pipeline/run/batch.
func (c *Client) PipelineRunBatch(ctx context.Context, tenders []TenderRef, languages []string) (*BatchAcceptedResponse, error) {
	reqBody := PipelineRunBatchRequest{
		Tenders:   tenders,
		Languages: languages,
	}
	return c.postBatchAccepted(ctx, "/pipeline/run/batch", reqBody, "pipeline run batch", map[string]interface{}{
		"tender_count": len(tenders),
		"languages":    languages,
	})
}

// PipelineAnalyze calls POST /pipeline/analyze to trigger analysis across stored tenders.
func (c *Client) PipelineAnalyze(ctx context.Context) (*PipelineActionResponse, error) {
	return c.triggerPipelineAction(ctx, "/pipeline/analyze", "pipeline analyze")
}

// PipelineDailyRun calls POST /pipeline/daily-run (sync + full pipeline on newly synced tenders).
func (c *Client) PipelineDailyRun(ctx context.Context) (*PipelineStartedResponse, error) {
	return c.triggerPipelineStarted(ctx, "/pipeline/daily-run", "pipeline daily-run")
}

// PipelineAuto calls POST /pipeline/auto (sync + complete all missing work, idempotent).
func (c *Client) PipelineAuto(ctx context.Context) (*PipelineStartedResponse, error) {
	return c.triggerPipelineStarted(ctx, "/pipeline/auto", "pipeline auto")
}

// GetPipelineLastRun returns the result of the last daily-run (GET /pipeline/last-run).
func (c *Client) GetPipelineLastRun(ctx context.Context) (*PipelineReportResponse, error) {
	return c.getPipelineReport(ctx, "/pipeline/last-run")
}

// GetPipelineLastAutoRun returns the result of the last auto run (GET /pipeline/last-auto-run).
func (c *Client) GetPipelineLastAutoRun(ctx context.Context) (*PipelineReportResponse, error) {
	return c.getPipelineReport(ctx, "/pipeline/last-auto-run")
}

// GetPipelineReport calls GET /pipeline/report.
func (c *Client) GetPipelineReport(ctx context.Context, window string) (*PipelineReportResponse, error) {
	return c.getPipelineReportWithQuery(ctx, "/pipeline/report", window)
}

// GetPipelineStats calls GET /pipeline/stats.
func (c *Client) GetPipelineStats(ctx context.Context, window string) (*PipelineStatsResponse, error) {
	url := c.config.APIBaseURL + "/pipeline/stats"
	if window = strings.TrimSpace(window); window != "" {
		url += "?window=" + window
	}

	httpResp, bodyBytes, err := c.doRawGet(ctx, url)
	if err != nil {
		return nil, err
	}
	defer httpResp.Body.Close()

	if httpResp.StatusCode == http.StatusBadRequest {
		return nil, apiNonSuccessError(http.StatusBadRequest, bodyBytes)
	}
	if httpResp.StatusCode >= 400 {
		return nil, apiNonSuccessError(httpResp.StatusCode, bodyBytes)
	}

	var result PipelineStatsResponse
	if len(bodyBytes) > 0 {
		if err := json.Unmarshal(bodyBytes, &result); err != nil {
			return nil, fmt.Errorf("ai_summarizer: failed to decode pipeline stats response: %w", err)
		}
	}

	return &result, nil
}

// GetPipelineMinioStats calls GET /pipeline/minio-stats.
func (c *Client) GetPipelineMinioStats(ctx context.Context) (PipelineMinioStatsResponse, error) {
	url := c.config.APIBaseURL + "/pipeline/minio-stats"
	httpResp, bodyBytes, err := c.doRawGet(ctx, url)
	if err != nil {
		return nil, err
	}
	defer httpResp.Body.Close()

	if httpResp.StatusCode >= 400 {
		return nil, apiNonSuccessError(httpResp.StatusCode, bodyBytes)
	}

	var result PipelineMinioStatsResponse
	if len(bodyBytes) > 0 {
		if err := json.Unmarshal(bodyBytes, &result); err != nil {
			return nil, fmt.Errorf("ai_summarizer: failed to decode pipeline minio stats response: %w", err)
		}
	}

	return result, nil
}

func (c *Client) postBatchAccepted(ctx context.Context, path string, reqBody any, label string, fields map[string]interface{}) (*BatchAcceptedResponse, error) {
	jsonBody, err := json.Marshal(reqBody)
	if err != nil {
		return nil, fmt.Errorf("ai_summarizer: failed to marshal %s request: %w", label, err)
	}

	url := c.config.APIBaseURL + path
	httpResp, bodyBytes, err := c.doRawPost(ctx, url, jsonBody)
	if err != nil {
		return nil, err
	}
	defer httpResp.Body.Close()

	if httpResp.StatusCode >= 400 {
		return nil, apiNonSuccessError(httpResp.StatusCode, bodyBytes)
	}

	var result BatchAcceptedResponse
	if len(bodyBytes) > 0 {
		if err := json.Unmarshal(bodyBytes, &result); err != nil {
			return nil, fmt.Errorf("ai_summarizer: failed to decode %s response: %w", label, err)
		}
	}

	logFields := map[string]interface{}{
		"path":   path,
		"status": result.Status,
		"count":  result.Count,
	}
	for k, v := range fields {
		logFields[k] = v
	}
	c.logger.Info("AI batch request accepted", logFields)

	return &result, nil
}

func (c *Client) triggerPipelineStarted(ctx context.Context, path, label string) (*PipelineStartedResponse, error) {
	url := c.config.APIBaseURL + path
	httpResp, bodyBytes, err := c.doRawPost(ctx, url, nil)
	if err != nil {
		return nil, err
	}
	defer httpResp.Body.Close()

	if httpResp.StatusCode == http.StatusConflict {
		return nil, fmt.Errorf("%w: status 409, body: %s", ErrPipelineJobRunning, string(bodyBytes))
	}
	if httpResp.StatusCode >= 400 {
		return nil, apiNonSuccessError(httpResp.StatusCode, bodyBytes)
	}

	var result PipelineStartedResponse
	if len(bodyBytes) > 0 {
		if err := json.Unmarshal(bodyBytes, &result); err != nil {
			return nil, fmt.Errorf("ai_summarizer: failed to decode %s response: %w", label, err)
		}
	}

	c.logger.Info("AI pipeline job started", map[string]interface{}{
		"path":   path,
		"status": result.Status,
		"report": result.Report,
	})

	return &result, nil
}

func (c *Client) getPipelineReport(ctx context.Context, path string) (*PipelineReportResponse, error) {
	return c.getPipelineReportWithQuery(ctx, path, "")
}

func (c *Client) getPipelineReportWithQuery(ctx context.Context, path, window string) (*PipelineReportResponse, error) {
	url := c.config.APIBaseURL + path
	if window = strings.TrimSpace(window); window != "" {
		url += "?window=" + window
	}

	httpResp, bodyBytes, err := c.doRawGet(ctx, url)
	if err != nil {
		return nil, err
	}
	defer httpResp.Body.Close()

	if httpResp.StatusCode == http.StatusBadRequest {
		return nil, apiNonSuccessError(http.StatusBadRequest, bodyBytes)
	}
	if httpResp.StatusCode >= 400 {
		return nil, apiNonSuccessError(httpResp.StatusCode, bodyBytes)
	}

	var result PipelineReportResponse
	if len(bodyBytes) > 0 {
		if err := json.Unmarshal(bodyBytes, &result); err != nil {
			return nil, fmt.Errorf("ai_summarizer: failed to decode pipeline report response: %w", err)
		}
	}

	return &result, nil
}

func (c *Client) doRawGet(ctx context.Context, url string) (*http.Response, []byte, error) {
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
	if err != nil {
		return nil, nil, fmt.Errorf("ai_summarizer: failed to create request: %w", err)
	}
	req.Header.Set("Accept", "application/json")

	httpResp, err := c.httpClient.Do(req)
	if err != nil {
		return nil, nil, fmt.Errorf("ai_summarizer: request error: %w", err)
	}

	bodyBytes, err := io.ReadAll(httpResp.Body)
	if err != nil {
		httpResp.Body.Close()
		return nil, nil, fmt.Errorf("ai_summarizer: failed to read response body: %w", err)
	}
	return httpResp, bodyBytes, nil
}

// FetchAnalyzeOnDemand calls POST /ai/analyze for one tender.
func (c *Client) FetchAnalyzeOnDemand(ctx context.Context, reqBody AnalyzeRequest) (*AnalyzeResponse, error) {
	jsonBody, err := json.Marshal(reqBody)
	if err != nil {
		return nil, fmt.Errorf("ai_summarizer: failed to marshal analyze request body: %w", err)
	}

	url := c.config.APIBaseURL + "/ai/analyze"
	httpResp, bodyBytes, err := c.doRawPost(ctx, url, jsonBody)
	if err != nil {
		return nil, err
	}
	defer httpResp.Body.Close()

	if httpResp.StatusCode == http.StatusNotFound {
		return nil, fmt.Errorf("%w: status 404, body: %s", ErrObjectNotFound, string(bodyBytes))
	}
	if httpResp.StatusCode >= 400 {
		return nil, apiNonSuccessError(httpResp.StatusCode, bodyBytes)
	}

	var result AnalyzeResponse
	if err := json.Unmarshal(bodyBytes, &result); err != nil {
		return nil, fmt.Errorf("ai_summarizer: failed to decode analyze response JSON: %w", err)
	}

	c.logger.Info("AI analyze request succeeded", map[string]interface{}{
		"contract_folder_id":    result.ContractFolderID,
		"notice_publication_id": result.NoticePublicationID,
		"iterations":            result.Iterations,
	})

	return &result, nil
}

// StartOnboarding calls POST /onboarding to start company profile indexing for recommendations.
func (c *Client) StartOnboarding(ctx context.Context, reqBody OnboardingRequest) (*OnboardingResponse, error) {
	jsonBody, err := json.Marshal(reqBody)
	if err != nil {
		return nil, fmt.Errorf("ai_summarizer: failed to marshal onboarding request body: %w", err)
	}

	url := c.config.APIBaseURL + "/onboarding"
	httpResp, bodyBytes, err := c.doRawPost(ctx, url, jsonBody)
	if err != nil {
		return nil, err
	}
	defer httpResp.Body.Close()

	if httpResp.StatusCode >= 400 {
		return nil, apiNonSuccessError(httpResp.StatusCode, bodyBytes)
	}

	var result OnboardingResponse
	if len(bodyBytes) > 0 {
		if err := json.Unmarshal(bodyBytes, &result); err != nil {
			return nil, fmt.Errorf("ai_summarizer: failed to decode onboarding response: %w", err)
		}
	}

	c.logger.Info("AI onboarding request succeeded", map[string]interface{}{
		"company_id": reqBody.CompanyID,
		"status":     result.Status,
	})

	return &result, nil
}

// FetchRecommendations calls POST /recommend to retrieve ranked tenders for a company.
func (c *Client) FetchRecommendations(ctx context.Context, reqBody RecommendRequest) ([]RecommendedTender, error) {
	jsonBody, err := json.Marshal(reqBody)
	if err != nil {
		return nil, fmt.Errorf("ai_summarizer: failed to marshal recommend request body: %w", err)
	}

	url := c.config.APIBaseURL + "/recommend"
	httpResp, bodyBytes, err := c.doRawPost(ctx, url, jsonBody)
	if err != nil {
		return nil, err
	}
	defer httpResp.Body.Close()

	if httpResp.StatusCode >= 400 {
		return nil, apiNonSuccessError(httpResp.StatusCode, bodyBytes)
	}

	var result []RecommendedTender
	if len(bodyBytes) > 0 {
		if err := json.Unmarshal(bodyBytes, &result); err != nil {
			return nil, fmt.Errorf("ai_summarizer: failed to decode recommend response: %w", err)
		}
	}

	c.logger.Info("AI recommend request succeeded", map[string]interface{}{
		"company_id": reqBody.CompanyID,
		"count":      len(result),
	})

	return result, nil
}

func (c *Client) triggerPipelineAction(ctx context.Context, path, label string) (*PipelineActionResponse, error) {
	url := c.config.APIBaseURL + path
	httpResp, bodyBytes, err := c.doRawPost(ctx, url, nil)
	if err != nil {
		return nil, err
	}
	defer httpResp.Body.Close()

	if httpResp.StatusCode >= 400 {
		return nil, apiNonSuccessError(httpResp.StatusCode, bodyBytes)
	}

	var result PipelineActionResponse
	if len(bodyBytes) > 0 {
		if err := json.Unmarshal(bodyBytes, &result); err != nil {
			return nil, fmt.Errorf("ai_summarizer: failed to decode %s response: %w", label, err)
		}
	}

	c.logger.Info("AI pipeline action triggered", map[string]interface{}{
		"path":   path,
		"status": result.Status,
	})

	return &result, nil
}

func (c *Client) doRawPost(ctx context.Context, url string, jsonBody []byte) (*http.Response, []byte, error) {
	var body io.Reader
	if len(jsonBody) > 0 {
		body = bytes.NewReader(jsonBody)
	}
	req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, body)
	if err != nil {
		return nil, nil, fmt.Errorf("ai_summarizer: failed to create request: %w", err)
	}
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Accept", "application/json")

	httpResp, err := c.httpClient.Do(req)
	if err != nil {
		return nil, nil, fmt.Errorf("ai_summarizer: request error: %w", err)
	}

	bodyBytes, err := io.ReadAll(httpResp.Body)
	if err != nil {
		httpResp.Body.Close()
		return nil, nil, fmt.Errorf("ai_summarizer: failed to read response body: %w", err)
	}
	return httpResp, bodyBytes, nil
}

// doPost performs a single POST request and parses the response.
func (c *Client) doPost(ctx context.Context, url string, jsonBody []byte) (*SummarizeResponse, error) {
	req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(jsonBody))
	if err != nil {
		return nil, fmt.Errorf("ai_summarizer: failed to create request: %w", err)
	}
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Accept", "application/json")

	c.logger.Debug("Sending AI summarize request", map[string]interface{}{
		"url": url,
	})

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

	bodyBytes, err := io.ReadAll(httpResp.Body)
	if err != nil {
		return nil, fmt.Errorf("ai_summarizer: failed to read response body: %w", err)
	}

	if httpResp.StatusCode == http.StatusNotFound {
		return nil, fmt.Errorf("%w: status 404, body: %s", ErrObjectNotFound, string(bodyBytes))
	}

	if httpResp.StatusCode >= 400 {
		return nil, apiNonSuccessError(httpResp.StatusCode, bodyBytes)
	}

	var result SummarizeResponse
	if err := json.Unmarshal(bodyBytes, &result); err != nil {
		return nil, fmt.Errorf("ai_summarizer: failed to decode response JSON: %w", err)
	}

	c.logger.Info("AI summarize request succeeded", map[string]interface{}{
		"contract_folder_id":    result.ContractFolderID,
		"notice_publication_id": result.ResolvedNoticePublicationID(),
		"summaries_count":       len(result.Summaries),
	})

	return &result, nil
}

// doTranslatePost performs a single translation POST request and parses the response.
func (c *Client) doTranslatePost(ctx context.Context, url string, jsonBody []byte, requestSource string) (*TranslateResponse, error) {
	req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(jsonBody))
	if err != nil {
		return nil, fmt.Errorf("ai_summarizer: failed to create translation request: %w", err)
	}
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Accept", "application/json")

	c.logger.Debug("Sending AI translate request", map[string]interface{}{
		"url": url,
	})

	httpResp, err := c.httpClient.Do(req)
	if err != nil {
		return nil, fmt.Errorf("ai_summarizer: translation request error: %w", err)
	}
	defer httpResp.Body.Close()

	bodyBytes, err := io.ReadAll(httpResp.Body)
	if err != nil {
		return nil, fmt.Errorf("ai_summarizer: failed to read translation response body: %w", err)
	}

	if httpResp.StatusCode >= 400 {
		return nil, apiNonSuccessError(httpResp.StatusCode, bodyBytes)
	}

	result, err := decodeTranslateResponse(bodyBytes)
	if err != nil {
		return nil, err
	}

	c.logger.Info("AI translate request succeeded", map[string]interface{}{
		"notice_publication_id": result.NoticePublicationID,
		"language":              result.Language,
	})

	if c.config.OnSuccessfulTranslation != nil {
		if err := c.config.OnSuccessfulTranslation(ctx, requestSource); err != nil {
			c.logger.Warn("Failed to increment AI translation success counter", map[string]interface{}{
				"notice_publication_id": result.NoticePublicationID,
				"language":              result.Language,
				"request_source":        requestSource,
				"error":                 err.Error(),
			})
		}
	}

	return result, nil
}

type translateAPIEnvelope struct {
	Success bool              `json:"success"`
	Message string            `json:"message"`
	Data    TranslateResponse `json:"data"`
}

func decodeTranslateResponse(bodyBytes []byte) (*TranslateResponse, error) {
	var envelope translateAPIEnvelope
	if err := json.Unmarshal(bodyBytes, &envelope); err == nil && envelope.Success && envelope.Data.Language != "" {
		return &envelope.Data, nil
	}

	var direct TranslateResponse
	if err := json.Unmarshal(bodyBytes, &direct); err != nil {
		return nil, fmt.Errorf("ai_summarizer: failed to decode translation response JSON: %w", err)
	}
	if strings.TrimSpace(direct.Language) == "" {
		return nil, fmt.Errorf("ai_summarizer: translation response missing language")
	}
	return &direct, nil
}
