package threadsapi

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

	"haixun-backend/internal/library/threadspost"
)

type PublishResult struct {
	MediaID   string
	Permalink string
}

type PublishTextInput struct {
	ThreadsUserID string
	AccessToken   string
	Text          string
}

type PublishReplyInput struct {
	ThreadsUserID string
	AccessToken   string
	ReplyToID     string
	Text          string
}

// PublishText posts a new text thread via Graph API.
func PublishText(ctx context.Context, in PublishTextInput) (*PublishResult, error) {
	userID := strings.TrimSpace(in.ThreadsUserID)
	token := strings.TrimSpace(in.AccessToken)
	text := strings.TrimSpace(in.Text)
	if userID == "" || token == "" {
		return nil, fmt.Errorf("threads api credentials incomplete")
	}
	if text == "" {
		return nil, fmt.Errorf("post text is required")
	}
	if err := threadspost.ValidatePublish(text); err != nil {
		return nil, err
	}

	containerID, err := createTextContainer(ctx, userID, token, text)
	if err != nil {
		return nil, err
	}
	if err := waitForContainerReady(ctx, containerID, token); err != nil {
		return nil, err
	}
	mediaID, err := publishContainer(ctx, userID, token, containerID)
	if err != nil {
		return nil, err
	}
	permalink, _ := fetchPermalink(ctx, mediaID, token)
	return &PublishResult{MediaID: mediaID, Permalink: permalink}, nil
}

// PublishReply posts a text reply to an existing Threads media via Graph API.
func PublishReply(ctx context.Context, in PublishReplyInput) (*PublishResult, error) {
	userID := strings.TrimSpace(in.ThreadsUserID)
	token := strings.TrimSpace(in.AccessToken)
	replyTo := strings.TrimSpace(in.ReplyToID)
	text := strings.TrimSpace(in.Text)
	if userID == "" || token == "" {
		return nil, fmt.Errorf("threads api credentials incomplete")
	}
	if replyTo == "" {
		return nil, fmt.Errorf("reply_to_id is required")
	}
	if text == "" {
		return nil, fmt.Errorf("reply text is required")
	}
	if err := threadspost.ValidateReply(text); err != nil {
		return nil, err
	}

	containerID, err := createReplyContainer(ctx, userID, token, replyTo, text)
	if err != nil {
		return nil, err
	}
	if err := waitForContainerReady(ctx, containerID, token); err != nil {
		return nil, err
	}
	mediaID, err := publishContainer(ctx, userID, token, containerID)
	if err != nil {
		return nil, err
	}
	permalink, _ := fetchPermalink(ctx, mediaID, token)
	return &PublishResult{MediaID: mediaID, Permalink: permalink}, nil
}

func createTextContainer(ctx context.Context, userID, token, text string) (string, error) {
	params := url.Values{}
	params.Set("access_token", token)
	params.Set("media_type", "TEXT")
	params.Set("text", text)
	endpoint := graphBaseURL + "/" + userID + "/threads?" + params.Encode()
	req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, nil)
	if err != nil {
		return "", err
	}
	body, status, err := doRequest(req)
	if err != nil {
		return "", err
	}
	if status != http.StatusOK {
		return "", parseAPIError(body, status)
	}
	var payload struct {
		ID string `json:"id"`
	}
	if err := json.Unmarshal(body, &payload); err != nil {
		return "", err
	}
	if strings.TrimSpace(payload.ID) == "" {
		return "", fmt.Errorf("threads api did not return container id")
	}
	return payload.ID, nil
}

func createReplyContainer(ctx context.Context, userID, token, replyTo, text string) (string, error) {
	params := url.Values{}
	params.Set("access_token", token)
	params.Set("media_type", "TEXT")
	params.Set("text", text)
	params.Set("reply_to_id", replyTo)

	endpoint := graphBaseURL + "/" + userID + "/threads?" + params.Encode()
	req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, nil)
	if err != nil {
		return "", err
	}
	body, status, err := doRequest(req)
	if err != nil {
		return "", err
	}
	if status != http.StatusOK {
		return "", parseAPIError(body, status)
	}
	var payload struct {
		ID string `json:"id"`
	}
	if err := json.Unmarshal(body, &payload); err != nil {
		return "", err
	}
	if strings.TrimSpace(payload.ID) == "" {
		return "", fmt.Errorf("threads api did not return container id")
	}
	return payload.ID, nil
}

func publishContainer(ctx context.Context, userID, token, containerID string) (string, error) {
	params := url.Values{}
	params.Set("access_token", token)
	params.Set("creation_id", containerID)
	endpoint := graphBaseURL + "/" + userID + "/threads_publish?" + params.Encode()
	req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, nil)
	if err != nil {
		return "", err
	}
	body, status, err := doRequest(req)
	if err != nil {
		return "", err
	}
	if status != http.StatusOK {
		return "", parseAPIError(body, status)
	}
	var payload struct {
		ID string `json:"id"`
	}
	if err := json.Unmarshal(body, &payload); err != nil {
		return "", err
	}
	if strings.TrimSpace(payload.ID) == "" {
		return "", fmt.Errorf("threads publish did not return media id")
	}
	return payload.ID, nil
}

func waitForContainerReady(ctx context.Context, containerID, token string) error {
	for attempt := 0; attempt < 20; attempt++ {
		if attempt > 0 {
			select {
			case <-ctx.Done():
				return ctx.Err()
			case <-time.After(3 * time.Second):
			}
		}
		params := url.Values{}
		params.Set("access_token", token)
		params.Set("fields", "status")
		endpoint := graphBaseURL + "/" + containerID + "?" + params.Encode()
		req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
		if err != nil {
			return err
		}
		body, status, err := doRequest(req)
		if err != nil {
			return err
		}
		if status != http.StatusOK {
			return parseAPIError(body, status)
		}
		var payload struct {
			Status string `json:"status"`
		}
		if err := json.Unmarshal(body, &payload); err != nil {
			return err
		}
		switch strings.ToUpper(strings.TrimSpace(payload.Status)) {
		case "FINISHED":
			return nil
		case "ERROR", "EXPIRED":
			return fmt.Errorf("threads container status %s", payload.Status)
		}
	}
	return fmt.Errorf("threads container not ready in time")
}

func fetchPermalink(ctx context.Context, mediaID, token string) (string, error) {
	params := url.Values{}
	params.Set("access_token", token)
	params.Set("fields", "permalink")
	endpoint := graphBaseURL + "/" + mediaID + "?" + params.Encode()
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
	if err != nil {
		return "", err
	}
	body, status, err := doRequest(req)
	if err != nil || status != http.StatusOK {
		return "", err
	}
	var payload struct {
		Permalink string `json:"permalink"`
	}
	if err := json.Unmarshal(body, &payload); err != nil {
		return "", err
	}
	return strings.TrimSpace(payload.Permalink), nil
}

func doRequest(req *http.Request) ([]byte, int, error) {
	client := &http.Client{Timeout: 25 * time.Second}
	res, err := client.Do(req)
	if err != nil {
		return nil, 0, err
	}
	defer res.Body.Close()
	body, err := io.ReadAll(io.LimitReader(res.Body, 1<<20))
	if err != nil {
		return nil, res.StatusCode, err
	}
	return body, res.StatusCode, nil
}
