package gitea

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

// Client wraps the Gitea API client for Bzzz task management
type Client struct {
	httpClient *http.Client
	baseURL    string
	token      string
	ctx        context.Context
	config     *Config
}

// Config holds Gitea integration configuration  
type Config struct {
	BaseURL     string // Gitea instance URL
	AccessToken string // Access token for API authentication
	Owner       string // Gitea organization/user
	Repository  string // Repository for task coordination
	
	// Task management settings
	TaskLabel       string // Label for Bzzz tasks (default: "bzzz-task")
	InProgressLabel string // Label for tasks in progress (default: "in-progress")
	CompletedLabel  string // Label for completed tasks (default: "completed")
	Assignee        string // Gitea username for task assignment
	
	// Branch management
	BaseBranch   string // Base branch for task branches (default: "main")
	BranchPrefix string // Prefix for task branches (default: "bzzz/task-")
}

// Task represents a Bzzz task as a Gitea issue
type Task struct {
	ID          int64     `json:"id"`
	Number      int64     `json:"number"`
	Title       string    `json:"title"`
	Description string    `json:"body"`
	State       string    `json:"state"`       // open, closed
	Labels      []Label   `json:"labels"`
	Assignee    *User     `json:"assignee"`
	CreatedAt   time.Time `json:"created_at"`
	UpdatedAt   time.Time `json:"updated_at"`
	
	// Bzzz-specific fields (parsed from body or labels)
	TaskType     string                 `json:"task_type"`
	Priority     int                    `json:"priority"`
	Requirements []string               `json:"requirements"`
	Deliverables []string               `json:"deliverables"`
	Context      map[string]interface{} `json:"context"`
	RequiredRole string                 `json:"required_role"`
	RequiredExpertise []string          `json:"required_expertise"`
}

// Label represents a Gitea issue label
type Label struct {
	ID          int64  `json:"id"`
	Name        string `json:"name"`
	Color       string `json:"color"`
	Description string `json:"description"`
}

// User represents a Gitea user
type User struct {
	ID       int64  `json:"id"`
	Login    string `json:"login"`
	FullName string `json:"full_name"`
	Email    string `json:"email"`
}

// Issue represents a Gitea issue
type Issue struct {
	ID        int64     `json:"id"`
	Number    int64     `json:"number"`
	Title     string    `json:"title"`
	Body      string    `json:"body"`
	State     string    `json:"state"`
	Labels    []Label   `json:"labels"`
	Assignee  *User     `json:"assignee"`
	CreatedAt time.Time `json:"created_at"`
	UpdatedAt time.Time `json:"updated_at"`
}

// Comment represents a Gitea issue comment
type Comment struct {
	ID        int64     `json:"id"`
	Body      string    `json:"body"`
	User      User      `json:"user"`
	CreatedAt time.Time `json:"created_at"`
	UpdatedAt time.Time `json:"updated_at"`
}

// NewClient creates a new Gitea client for Bzzz integration
func NewClient(ctx context.Context, config *Config) (*Client, error) {
	if config.BaseURL == "" {
		return nil, fmt.Errorf("Gitea base URL is required")
	}
	if config.AccessToken == "" {
		return nil, fmt.Errorf("Gitea access token is required")
	}
	if config.Owner == "" || config.Repository == "" {
		return nil, fmt.Errorf("Gitea owner and repository are required")
	}
	
	// Set defaults
	if config.TaskLabel == "" {
		config.TaskLabel = "bzzz-task"
	}
	if config.InProgressLabel == "" {
		config.InProgressLabel = "in-progress"
	}
	if config.CompletedLabel == "" {
		config.CompletedLabel = "completed"
	}
	if config.BaseBranch == "" {
		config.BaseBranch = "main"
	}
	if config.BranchPrefix == "" {
		config.BranchPrefix = "bzzz/task-"
	}
	
	client := &Client{
		httpClient: &http.Client{Timeout: 30 * time.Second},
		baseURL:    config.BaseURL,
		token:      config.AccessToken,
		ctx:        ctx,
		config:     config,
	}
	
	// Verify access to repository
	if err := client.verifyAccess(); err != nil {
		return nil, fmt.Errorf("failed to verify Gitea access: %w", err)
	}
	
	return client, nil
}

// verifyAccess checks if we can access the configured repository
func (c *Client) verifyAccess() error {
	url := fmt.Sprintf("%s/api/v1/repos/%s/%s", c.baseURL, c.config.Owner, c.config.Repository)
	
	req, err := http.NewRequestWithContext(c.ctx, "GET", url, nil)
	if err != nil {
		return fmt.Errorf("failed to create request: %w", err)
	}
	
	req.Header.Set("Authorization", "token "+c.token)
	req.Header.Set("Content-Type", "application/json")
	
	resp, err := c.httpClient.Do(req)
	if err != nil {
		return fmt.Errorf("failed to make request: %w", err)
	}
	defer resp.Body.Close()
	
	if resp.StatusCode != http.StatusOK {
		body, _ := io.ReadAll(resp.Body)
		return fmt.Errorf("cannot access repository %s/%s: HTTP %d - %s (URL: %s)", 
			c.config.Owner, c.config.Repository, resp.StatusCode, string(body), url)
	}
	
	return nil
}

// ListAvailableTasks returns unassigned Bzzz tasks
func (c *Client) ListAvailableTasks() ([]*Task, error) {
	apiURL := fmt.Sprintf("%s/api/v1/repos/%s/%s/issues", c.baseURL, c.config.Owner, c.config.Repository)
	
	// Add query parameters
	params := url.Values{}
	params.Add("state", "open")
	params.Add("labels", c.config.TaskLabel)
	params.Add("limit", "50")
	
	req, err := http.NewRequestWithContext(c.ctx, "GET", apiURL+"?"+params.Encode(), nil)
	if err != nil {
		return nil, fmt.Errorf("failed to create request: %w", err)
	}
	
	req.Header.Set("Authorization", "token "+c.token)
	req.Header.Set("Content-Type", "application/json")
	
	resp, err := c.httpClient.Do(req)
	if err != nil {
		return nil, fmt.Errorf("failed to make request: %w", err)
	}
	defer resp.Body.Close()
	
	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("failed to list issues: HTTP %d", resp.StatusCode)
	}
	
	var issues []Issue
	if err := json.NewDecoder(resp.Body).Decode(&issues); err != nil {
		return nil, fmt.Errorf("failed to decode response: %w", err)
	}
	
	// Filter for unassigned tasks and convert to Task format
	tasks := make([]*Task, 0, len(issues))
	for _, issue := range issues {
		// Skip if already assigned
		if issue.Assignee != nil {
			continue
		}
		
		// Check if it has the bzzz-task label
		hasBzzzLabel := false
		for _, label := range issue.Labels {
			if label.Name == c.config.TaskLabel {
				hasBzzzLabel = true
				break
			}
		}
		
		if hasBzzzLabel {
			tasks = append(tasks, c.issueToTask(&issue))
		}
	}
	
	return tasks, nil
}

// ClaimTask atomically assigns a task to an agent
func (c *Client) ClaimTask(issueNumber int64, agentID string) (*Task, error) {
	// Get current issue state
	issue, err := c.getIssue(issueNumber)
	if err != nil {
		return nil, fmt.Errorf("failed to get issue: %w", err)
	}
	
	// Check if already assigned
	if issue.Assignee != nil {
		return nil, fmt.Errorf("task already assigned to %s", issue.Assignee.Login)
	}
	
	// Add in-progress label
	currentLabels := make([]string, 0, len(issue.Labels)+1)
	for _, label := range issue.Labels {
		currentLabels = append(currentLabels, label.Name)
	}
	currentLabels = append(currentLabels, c.config.InProgressLabel)
	
	// Update the issue with labels (assignment through API may require different approach)
	if err := c.updateIssueLabels(issueNumber, currentLabels); err != nil {
		return nil, fmt.Errorf("failed to update issue labels: %w", err)
	}
	
	// Add a comment to track which Bzzz agent claimed this task
	claimComment := fmt.Sprintf("🐝 **Task claimed by Bzzz agent:** `%s`\n\nThis task has been automatically claimed by the Bzzz P2P task coordination system.\n\n**Agent Details:**\n- Agent ID: `%s`\n- Claimed at: %s", agentID, agentID, time.Now().Format(time.RFC3339))
	
	if err := c.addComment(issueNumber, claimComment); err != nil {
		// Log error but don't fail the claim
		fmt.Printf("⚠️ Failed to add claim comment: %v\n", err)
	}
	
	// Get updated issue
	updatedIssue, err := c.getIssue(issueNumber)
	if err != nil {
		return nil, fmt.Errorf("failed to get updated issue: %w", err)
	}
	
	return c.issueToTask(updatedIssue), nil
}

// CompleteTask marks a task as completed
func (c *Client) CompleteTask(issueNumber int64, agentID string, results map[string]interface{}) error {
	// Get current issue
	issue, err := c.getIssue(issueNumber)
	if err != nil {
		return fmt.Errorf("failed to get issue: %w", err)
	}
	
	// Remove in-progress label, add completed label
	newLabels := make([]string, 0, len(issue.Labels))
	for _, label := range issue.Labels {
		if label.Name != c.config.InProgressLabel {
			newLabels = append(newLabels, label.Name)
		}
	}
	newLabels = append(newLabels, c.config.CompletedLabel)
	
	// Update labels
	if err := c.updateIssueLabels(issueNumber, newLabels); err != nil {
		return fmt.Errorf("failed to update issue labels: %w", err)
	}
	
	// Add completion comment
	comment := c.formatCompletionComment(agentID, results)
	if err := c.addComment(issueNumber, comment); err != nil {
		return fmt.Errorf("failed to add completion comment: %w", err)
	}
	
	// Close the issue
	if err := c.closeIssue(issueNumber); err != nil {
		return fmt.Errorf("failed to close issue: %w", err)
	}
	
	return nil
}

// getIssue retrieves a single issue by number
func (c *Client) getIssue(issueNumber int64) (*Issue, error) {
	url := fmt.Sprintf("%s/api/v1/repos/%s/%s/issues/%d", 
		c.baseURL, c.config.Owner, c.config.Repository, issueNumber)
	
	req, err := http.NewRequestWithContext(c.ctx, "GET", url, nil)
	if err != nil {
		return nil, fmt.Errorf("failed to create request: %w", err)
	}
	
	req.Header.Set("Authorization", "token "+c.token)
	req.Header.Set("Content-Type", "application/json")
	
	resp, err := c.httpClient.Do(req)
	if err != nil {
		return nil, fmt.Errorf("failed to make request: %w", err)
	}
	defer resp.Body.Close()
	
	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("failed to get issue: HTTP %d", resp.StatusCode)
	}
	
	var issue Issue
	if err := json.NewDecoder(resp.Body).Decode(&issue); err != nil {
		return nil, fmt.Errorf("failed to decode response: %w", err)
	}
	
	return &issue, nil
}

// updateIssueLabels updates the labels on an issue
func (c *Client) updateIssueLabels(issueNumber int64, labels []string) error {
	url := fmt.Sprintf("%s/api/v1/repos/%s/%s/issues/%d", 
		c.baseURL, c.config.Owner, c.config.Repository, issueNumber)
	
	updateData := map[string]interface{}{
		"labels": labels,
	}
	
	jsonData, err := json.Marshal(updateData)
	if err != nil {
		return fmt.Errorf("failed to marshal update data: %w", err)
	}
	
	req, err := http.NewRequestWithContext(c.ctx, "PATCH", url, bytes.NewBuffer(jsonData))
	if err != nil {
		return fmt.Errorf("failed to create request: %w", err)
	}
	
	req.Header.Set("Authorization", "token "+c.token)
	req.Header.Set("Content-Type", "application/json")
	
	resp, err := c.httpClient.Do(req)
	if err != nil {
		return fmt.Errorf("failed to make request: %w", err)
	}
	defer resp.Body.Close()
	
	if resp.StatusCode != http.StatusCreated && resp.StatusCode != http.StatusOK {
		body, _ := io.ReadAll(resp.Body)
		return fmt.Errorf("failed to update issue labels: HTTP %d - %s", resp.StatusCode, string(body))
	}
	
	return nil
}

// closeIssue closes an issue
func (c *Client) closeIssue(issueNumber int64) error {
	url := fmt.Sprintf("%s/api/v1/repos/%s/%s/issues/%d", 
		c.baseURL, c.config.Owner, c.config.Repository, issueNumber)
	
	updateData := map[string]interface{}{
		"state": "closed",
	}
	
	jsonData, err := json.Marshal(updateData)
	if err != nil {
		return fmt.Errorf("failed to marshal update data: %w", err)
	}
	
	req, err := http.NewRequestWithContext(c.ctx, "PATCH", url, bytes.NewBuffer(jsonData))
	if err != nil {
		return fmt.Errorf("failed to create request: %w", err)
	}
	
	req.Header.Set("Authorization", "token "+c.token)
	req.Header.Set("Content-Type", "application/json")
	
	resp, err := c.httpClient.Do(req)
	if err != nil {
		return fmt.Errorf("failed to make request: %w", err)
	}
	defer resp.Body.Close()
	
	if resp.StatusCode != http.StatusCreated && resp.StatusCode != http.StatusOK {
		return fmt.Errorf("failed to close issue: HTTP %d", resp.StatusCode)
	}
	
	return nil
}

// addComment adds a comment to an issue
func (c *Client) addComment(issueNumber int64, body string) error {
	url := fmt.Sprintf("%s/api/v1/repos/%s/%s/issues/%d/comments", 
		c.baseURL, c.config.Owner, c.config.Repository, issueNumber)
	
	commentData := map[string]interface{}{
		"body": body,
	}
	
	jsonData, err := json.Marshal(commentData)
	if err != nil {
		return fmt.Errorf("failed to marshal comment data: %w", err)
	}
	
	req, err := http.NewRequestWithContext(c.ctx, "POST", url, bytes.NewBuffer(jsonData))
	if err != nil {
		return fmt.Errorf("failed to create request: %w", err)
	}
	
	req.Header.Set("Authorization", "token "+c.token)
	req.Header.Set("Content-Type", "application/json")
	
	resp, err := c.httpClient.Do(req)
	if err != nil {
		return fmt.Errorf("failed to make request: %w", err)
	}
	defer resp.Body.Close()
	
	if resp.StatusCode != http.StatusCreated {
		return fmt.Errorf("failed to add comment: HTTP %d", resp.StatusCode)
	}
	
	return nil
}

// formatCompletionComment formats task completion results
func (c *Client) formatCompletionComment(agentID string, results map[string]interface{}) string {
	comment := fmt.Sprintf("✅ **Task completed by agent: %s**\n\n", agentID)
	comment += fmt.Sprintf("**Completion time:** %s\n\n", time.Now().Format(time.RFC3339))
	
	if len(results) > 0 {
		comment += "**Results:**\n"
		for key, value := range results {
			comment += fmt.Sprintf("- **%s:** %v\n", key, value)
		}
		comment += "\n"
	}
	
	comment += "---\n*Completed by Bzzz P2P Task Coordination System*"
	return comment
}

// issueToTask converts a Gitea issue to a Bzzz task
func (c *Client) issueToTask(issue *Issue) *Task {
	task := &Task{
		ID:          issue.ID,
		Number:      issue.Number,
		Title:       issue.Title,
		Description: issue.Body,
		State:       issue.State,
		Labels:      issue.Labels,
		Assignee:    issue.Assignee,
		CreatedAt:   issue.CreatedAt,
		UpdatedAt:   issue.UpdatedAt,
		Priority:    5, // Default priority
	}
	
	// Parse task metadata from labels and body
	c.parseTaskMetadata(task, issue)
	
	return task
}

// parseTaskMetadata extracts Bzzz-specific metadata from issue labels and body
func (c *Client) parseTaskMetadata(task *Task, issue *Issue) {
	// Parse labels for metadata
	for _, label := range issue.Labels {
		switch {
		case label.Name == "frontend":
			task.RequiredRole = "frontend_developer"
			task.RequiredExpertise = []string{"frontend", "ui_development"}
		case label.Name == "backend":
			task.RequiredRole = "backend_developer"
			task.RequiredExpertise = []string{"backend", "api_development"}
		case label.Name == "security":
			task.RequiredRole = "security_expert"
			task.RequiredExpertise = []string{"security", "vulnerability_analysis"}
		case label.Name == "design":
			task.RequiredRole = "ui_ux_designer"
			task.RequiredExpertise = []string{"design", "user_experience"}
		case label.Name == "devops":
			task.RequiredRole = "devops_engineer"
			task.RequiredExpertise = []string{"deployment", "infrastructure"}
		case label.Name == "documentation":
			task.RequiredRole = "technical_writer"
			task.RequiredExpertise = []string{"documentation", "technical_writing"}
		case label.Name == "bug":
			task.TaskType = "bug_fix"
			task.RequiredRole = "qa_engineer"
			task.RequiredExpertise = []string{"testing", "debugging"}
		case label.Name == "enhancement":
			task.TaskType = "feature"
		case label.Name == "architecture":
			task.RequiredRole = "senior_software_architect"
			task.RequiredExpertise = []string{"architecture", "system_design"}
		case label.Name == "priority-high":
			task.Priority = 8
		case label.Name == "priority-urgent":
			task.Priority = 10
		case label.Name == "priority-low":
			task.Priority = 3
		}
	}
	
	// Set default task type if not set
	if task.TaskType == "" {
		task.TaskType = "general"
	}
	
	// Set default role if not set
	if task.RequiredRole == "" {
		task.RequiredRole = "full_stack_engineer"
		task.RequiredExpertise = []string{"general_development"}
	}
}