//go:build e2e

package client

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

// Task is a task board item.
type Task struct {
	ID         string `json:"id"`
	ChannelID  string `json:"channelId"`
	TaskNumber int64  `json:"taskNumber"`
	Title      string `json:"title"`
	Status     string `json:"status"`
}

// TaskPage is a list of tasks.
type TaskPage struct {
	Tasks []Task `json:"tasks"`
}

type taskBrowsePage struct {
	Rows       []Task `json:"rows"`
	HasMore    bool   `json:"hasMore"`
	NextCursor string `json:"nextCursor"`
}

// TaskResponse wraps one task from lifecycle endpoints.
type TaskResponse struct {
	Task Task `json:"task"`
}

// TaskDetailEnvelope is a minimal task detail response shape.
type TaskDetailEnvelope struct {
	Detail struct {
		Task Task `json:"task"`
	} `json:"detail"`
}

// CreateChannelTasks creates tasks in a channel from simple titles.
func (c *Client) CreateChannelTasks(ctx context.Context, channelID string, titles ...string) (TaskPage, int, []byte, error) {
	items := make([]map[string]string, 0, len(titles))
	for _, title := range titles {
		items = append(items, map[string]string{"title": title})
	}
	req := map[string]any{"tasks": items}
	var out TaskPage
	path := fmt.Sprintf("/api/v1/workspaces/%s/channels/%s/tasks", c.workspace, channelID)
	status, raw, err := c.DoJSON(ctx, "POST", path, req, &out)
	return out, status, raw, err
}

// ListWorkspaceTasks returns tasks visible across the workspace.
func (c *Client) ListWorkspaceTasks(ctx context.Context) (TaskPage, int, []byte, error) {
	return c.listAllTasks(ctx, fmt.Sprintf("/api/v1/workspaces/%s/tasks/workspace/browse", c.workspace), true)
}

// ListChannelTasks returns tasks for one channel.
func (c *Client) ListChannelTasks(ctx context.Context, channelID string) (TaskPage, int, []byte, error) {
	return c.listAllTasks(ctx, fmt.Sprintf("/api/v1/workspaces/%s/channels/%s/tasks", c.workspace, channelID), false)
}

func (c *Client) listAllTasks(ctx context.Context, path string, post bool) (TaskPage, int, []byte, error) {
	out := TaskPage{Tasks: []Task{}}
	cursor := ""
	for {
		var page taskBrowsePage
		requestPath := path
		method := "GET"
		var body any
		if post {
			method = "POST"
			body = map[string]any{"limit": 100, "cursor": cursor}
		} else {
			requestPath += "?limit=100"
			if cursor != "" {
				requestPath += "&cursor=" + url.QueryEscape(cursor)
			}
		}
		status, raw, err := c.DoJSON(ctx, method, requestPath, body, &page)
		if err != nil || status < 200 || status >= 300 {
			return out, status, raw, err
		}
		out.Tasks = append(out.Tasks, page.Rows...)
		if !page.HasMore {
			return out, status, raw, nil
		}
		if page.NextCursor == "" || page.NextCursor == cursor {
			return out, status, raw, fmt.Errorf("task browse response missing next cursor")
		}
		cursor = page.NextCursor
	}
}

// ClaimTask claims a task for the current actor.
func (c *Client) ClaimTask(ctx context.Context, taskID string) (TaskResponse, int, []byte, error) {
	var out TaskResponse
	path := fmt.Sprintf("/api/v1/workspaces/%s/tasks/%s/claim", c.workspace, taskID)
	status, raw, err := c.DoJSON(ctx, "PATCH", path, nil, &out)
	return out, status, raw, err
}

// UnclaimTask removes current claim from a task.
func (c *Client) UnclaimTask(ctx context.Context, taskID string) (TaskResponse, int, []byte, error) {
	var out TaskResponse
	path := fmt.Sprintf("/api/v1/workspaces/%s/tasks/%s/unclaim", c.workspace, taskID)
	status, raw, err := c.DoJSON(ctx, "PATCH", path, nil, &out)
	return out, status, raw, err
}

// GetTaskDetail fetches task detail payload.
func (c *Client) GetTaskDetail(ctx context.Context, taskID string) (TaskDetailEnvelope, int, []byte, error) {
	var out TaskDetailEnvelope
	path := fmt.Sprintf("/api/v1/workspaces/%s/tasks/%s", c.workspace, taskID)
	status, raw, err := c.DoJSON(ctx, "GET", path, nil, &out)
	return out, status, raw, err
}
