package agentic

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

	"github.com/host-uk/core/pkg/log"
)

// Client is the API client for the core-agentic service.
type Client struct {
	// BaseURL is the base URL of the API server.
	BaseURL string
	// Token is the authentication token.
	Token string
	// HTTPClient is the HTTP client used for requests.
	HTTPClient *http.Client
	// AgentID is the identifier for this agent when claiming tasks.
	AgentID string
}

// NewClient creates a new agentic API client with the given base URL and token.
func NewClient(baseURL, token string) *Client {
	return &Client{
		BaseURL: strings.TrimSuffix(baseURL, "/"),
		Token:   token,
		HTTPClient: &http.Client{
			Timeout: 30 * time.Second,
		},
	}
}

// NewClientFromConfig creates a new client from a Config struct.
func NewClientFromConfig(cfg *Config) *Client {
	client := NewClient(cfg.BaseURL, cfg.Token)
	client.AgentID = cfg.AgentID
	return client
}

// ListTasks retrieves a list of tasks matching the given options.
func (c *Client) ListTasks(ctx context.Context, opts ListOptions) ([]Task, error) {
	const op = "agentic.Client.ListTasks"

	// Build query parameters
	params := url.Values{}
	if opts.Status != "" {
		params.Set("status", string(opts.Status))
	}
	if opts.Priority != "" {
		params.Set("priority", string(opts.Priority))
	}
	if opts.Project != "" {
		params.Set("project", opts.Project)
	}
	if opts.ClaimedBy != "" {
		params.Set("claimed_by", opts.ClaimedBy)
	}
	if opts.Limit > 0 {
		params.Set("limit", strconv.Itoa(opts.Limit))
	}
	if len(opts.Labels) > 0 {
		params.Set("labels", strings.Join(opts.Labels, ","))
	}

	endpoint := c.BaseURL + "/api/tasks"
	if len(params) > 0 {
		endpoint += "?" + params.Encode()
	}

	req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
	if err != nil {
		return nil, log.E(op, "failed to create request", err)
	}

	c.setHeaders(req)

	resp, err := c.HTTPClient.Do(req)
	if err != nil {
		return nil, log.E(op, "request failed", err)
	}
	defer func() { _ = resp.Body.Close() }()

	if err := c.checkResponse(resp); err != nil {
		return nil, log.E(op, "API error", err)
	}

	var tasks []Task
	if err := json.NewDecoder(resp.Body).Decode(&tasks); err != nil {
		return nil, log.E(op, "failed to decode response", err)
	}

	return tasks, nil
}

// GetTask retrieves a single task by its ID.
func (c *Client) GetTask(ctx context.Context, id string) (*Task, error) {
	const op = "agentic.Client.GetTask"

	if id == "" {
		return nil, log.E(op, "task ID is required", nil)
	}

	endpoint := fmt.Sprintf("%s/api/tasks/%s", c.BaseURL, url.PathEscape(id))

	req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
	if err != nil {
		return nil, log.E(op, "failed to create request", err)
	}

	c.setHeaders(req)

	resp, err := c.HTTPClient.Do(req)
	if err != nil {
		return nil, log.E(op, "request failed", err)
	}
	defer func() { _ = resp.Body.Close() }()

	if err := c.checkResponse(resp); err != nil {
		return nil, log.E(op, "API error", err)
	}

	var task Task
	if err := json.NewDecoder(resp.Body).Decode(&task); err != nil {
		return nil, log.E(op, "failed to decode response", err)
	}

	return &task, nil
}

// ClaimTask claims a task for the current agent.
func (c *Client) ClaimTask(ctx context.Context, id string) (*Task, error) {
	const op = "agentic.Client.ClaimTask"

	if id == "" {
		return nil, log.E(op, "task ID is required", nil)
	}

	endpoint := fmt.Sprintf("%s/api/tasks/%s/claim", c.BaseURL, url.PathEscape(id))

	// Include agent ID in the claim request if available
	var body io.Reader
	if c.AgentID != "" {
		data, _ := json.Marshal(map[string]string{"agent_id": c.AgentID})
		body = bytes.NewReader(data)
	}

	req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, body)
	if err != nil {
		return nil, log.E(op, "failed to create request", err)
	}

	c.setHeaders(req)
	if body != nil {
		req.Header.Set("Content-Type", "application/json")
	}

	resp, err := c.HTTPClient.Do(req)
	if err != nil {
		return nil, log.E(op, "request failed", err)
	}
	defer func() { _ = resp.Body.Close() }()

	if err := c.checkResponse(resp); err != nil {
		return nil, log.E(op, "API error", err)
	}

	// Read body once to allow multiple decode attempts
	bodyData, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, log.E(op, "failed to read response", err)
	}

	// Try decoding as ClaimResponse first
	var result ClaimResponse
	if err := json.Unmarshal(bodyData, &result); err == nil && result.Task != nil {
		return result.Task, nil
	}

	// Try decoding as just a Task for simpler API responses
	var task Task
	if err := json.Unmarshal(bodyData, &task); err != nil {
		return nil, log.E(op, "failed to decode response", err)
	}

	return &task, nil
}

// UpdateTask updates a task with new status, progress, or notes.
func (c *Client) UpdateTask(ctx context.Context, id string, update TaskUpdate) error {
	const op = "agentic.Client.UpdateTask"

	if id == "" {
		return log.E(op, "task ID is required", nil)
	}

	endpoint := fmt.Sprintf("%s/api/tasks/%s", c.BaseURL, url.PathEscape(id))

	data, err := json.Marshal(update)
	if err != nil {
		return log.E(op, "failed to marshal update", err)
	}

	req, err := http.NewRequestWithContext(ctx, http.MethodPatch, endpoint, bytes.NewReader(data))
	if err != nil {
		return log.E(op, "failed to create request", err)
	}

	c.setHeaders(req)
	req.Header.Set("Content-Type", "application/json")

	resp, err := c.HTTPClient.Do(req)
	if err != nil {
		return log.E(op, "request failed", err)
	}
	defer func() { _ = resp.Body.Close() }()

	if err := c.checkResponse(resp); err != nil {
		return log.E(op, "API error", err)
	}

	return nil
}

// CompleteTask marks a task as completed with the given result.
func (c *Client) CompleteTask(ctx context.Context, id string, result TaskResult) error {
	const op = "agentic.Client.CompleteTask"

	if id == "" {
		return log.E(op, "task ID is required", nil)
	}

	endpoint := fmt.Sprintf("%s/api/tasks/%s/complete", c.BaseURL, url.PathEscape(id))

	data, err := json.Marshal(result)
	if err != nil {
		return log.E(op, "failed to marshal result", err)
	}

	req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, bytes.NewReader(data))
	if err != nil {
		return log.E(op, "failed to create request", err)
	}

	c.setHeaders(req)
	req.Header.Set("Content-Type", "application/json")

	resp, err := c.HTTPClient.Do(req)
	if err != nil {
		return log.E(op, "request failed", err)
	}
	defer func() { _ = resp.Body.Close() }()

	if err := c.checkResponse(resp); err != nil {
		return log.E(op, "API error", err)
	}

	return nil
}

// setHeaders adds common headers to the request.
func (c *Client) setHeaders(req *http.Request) {
	req.Header.Set("Authorization", "Bearer "+c.Token)
	req.Header.Set("Accept", "application/json")
	req.Header.Set("User-Agent", "core-agentic-client/1.0")
}

// checkResponse checks if the response indicates an error.
func (c *Client) checkResponse(resp *http.Response) error {
	if resp.StatusCode >= 200 && resp.StatusCode < 300 {
		return nil
	}

	body, _ := io.ReadAll(resp.Body)

	// Try to parse as APIError
	var apiErr APIError
	if err := json.Unmarshal(body, &apiErr); err == nil && apiErr.Message != "" {
		apiErr.Code = resp.StatusCode
		return &apiErr
	}

	// Return generic error
	return &APIError{
		Code:    resp.StatusCode,
		Message: fmt.Sprintf("HTTP %d: %s", resp.StatusCode, http.StatusText(resp.StatusCode)),
		Details: string(body),
	}
}

// Ping tests the connection to the API server.
func (c *Client) Ping(ctx context.Context) error {
	const op = "agentic.Client.Ping"

	endpoint := c.BaseURL + "/api/health"

	req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
	if err != nil {
		return log.E(op, "failed to create request", err)
	}

	c.setHeaders(req)

	resp, err := c.HTTPClient.Do(req)
	if err != nil {
		return log.E(op, "request failed", err)
	}
	defer func() { _ = resp.Body.Close() }()

	if resp.StatusCode >= 400 {
		return log.E(op, fmt.Sprintf("server returned status %d", resp.StatusCode), nil)
	}

	return nil
}
