package gitea

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

	"github.com/chorus-services/whoosh/internal/config"
	"github.com/rs/zerolog/log"
)

// Client represents a Gitea API client
type Client struct {
	baseURL string
	token   string
	client  *http.Client
	config  config.GITEAConfig
}

// 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"`
	Assignees   []User          `json:"assignees"`
	CreatedAt   time.Time       `json:"created_at"`
	UpdatedAt   time.Time       `json:"updated_at"`
	ClosedAt    *time.Time      `json:"closed_at"`
	HTMLURL     string          `json:"html_url"`
	User        User            `json:"user"`
	Repository  IssueRepository `json:"repository,omitempty"`
}

// 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"`
	AvatarURL string `json:"avatar_url"`
}

// Repository represents a Gitea repository
type Repository struct {
	ID          int64  `json:"id"`
	Name        string `json:"name"`
	FullName    string `json:"full_name"`
	Owner       User   `json:"owner"`
	Description string `json:"description"`
	Private     bool   `json:"private"`
	HTMLURL     string `json:"html_url"`
	CloneURL    string `json:"clone_url"`
	SSHURL      string `json:"ssh_url"`
	Language    string `json:"language"`
}

// IssueRepository represents the simplified repository info in issue responses
type IssueRepository struct {
	ID       int64  `json:"id"`
	Name     string `json:"name"`
	FullName string `json:"full_name"`
	Owner    string `json:"owner"` // Note: This is a string, not a User object
}

// NewClient creates a new Gitea API client
func NewClient(cfg config.GITEAConfig) *Client {
	token := cfg.Token
	// Load token from file if TokenFile is specified and Token is empty
	if token == "" && cfg.TokenFile != "" {
		if fileToken, err := os.ReadFile(cfg.TokenFile); err == nil {
			token = strings.TrimSpace(string(fileToken))
		}
	}

	return &Client{
		baseURL: cfg.BaseURL,
		token:   token,
		config:  cfg,
		client: &http.Client{
			Timeout: 30 * time.Second,
		},
	}
}

// makeRequest makes an authenticated request to the Gitea API with retry logic
func (c *Client) makeRequest(ctx context.Context, method, endpoint string) (*http.Response, error) {
	url := fmt.Sprintf("%s/api/v1%s", c.baseURL, endpoint)
	
	if c.config.DebugURLs {
		log.Debug().
			Str("method", method).
			Str("url", url).
			Msg("Making Gitea API request")
	}
	
	var lastErr error
	for attempt := 0; attempt <= c.config.MaxRetries; attempt++ {
		if attempt > 0 {
			select {
			case <-ctx.Done():
				return nil, ctx.Err()
			case <-time.After(c.config.RetryDelay):
				// Continue with retry
			}
			
			if c.config.DebugURLs {
				log.Debug().
					Int("attempt", attempt).
					Str("url", url).
					Msg("Retrying Gitea API request")
			}
		}
		
		req, err := http.NewRequestWithContext(ctx, method, url, nil)
		if err != nil {
			return nil, fmt.Errorf("failed to create request: %w", err)
		}
		
		if c.token != "" {
			req.Header.Set("Authorization", "token "+c.token)
		}
		req.Header.Set("Content-Type", "application/json")
		req.Header.Set("Accept", "application/json")
		
		resp, err := c.client.Do(req)
		if err != nil {
			lastErr = fmt.Errorf("failed to make request: %w", err)
			log.Warn().
				Err(err).
				Str("url", url).
				Int("attempt", attempt).
				Msg("Gitea API request failed")
			continue
		}
		
		if resp.StatusCode >= 400 {
			defer resp.Body.Close()
			lastErr = fmt.Errorf("API request failed with status %d", resp.StatusCode)
			
			// Only retry on specific status codes (5xx errors, rate limiting)
			if resp.StatusCode >= 500 || resp.StatusCode == 429 {
				log.Warn().
					Int("status_code", resp.StatusCode).
					Str("url", url).
					Int("attempt", attempt).
					Msg("Retryable Gitea API error")
				continue
			}
			
			// Don't retry on 4xx errors (client errors)
			return nil, lastErr
		}
		
		// Success
		return resp, nil
	}
	
	return nil, fmt.Errorf("max retries exceeded: %w", lastErr)
}

// GetRepository retrieves repository information
func (c *Client) GetRepository(ctx context.Context, owner, repo string) (*Repository, error) {
	endpoint := fmt.Sprintf("/repos/%s/%s", url.PathEscape(owner), url.PathEscape(repo))
	
	resp, err := c.makeRequest(ctx, "GET", endpoint)
	if err != nil {
		return nil, fmt.Errorf("failed to get repository: %w", err)
	}
	defer resp.Body.Close()
	
	var repository Repository
	if err := json.NewDecoder(resp.Body).Decode(&repository); err != nil {
		return nil, fmt.Errorf("failed to decode repository: %w", err)
	}
	
	return &repository, nil
}

// GetIssues retrieves issues from a repository with hardening features
func (c *Client) GetIssues(ctx context.Context, owner, repo string, opts IssueListOptions) ([]Issue, error) {
	endpoint := fmt.Sprintf("/repos/%s/%s/issues", url.PathEscape(owner), url.PathEscape(repo))
	
	// Add query parameters
	params := url.Values{}
	if opts.State != "" {
		params.Set("state", opts.State)
	}
	
	// EAGER_FILTER: Apply label pre-filtering at the API level for efficiency
	if c.config.EagerFilter && opts.Labels != "" {
		params.Set("labels", opts.Labels)
		if c.config.DebugURLs {
			log.Debug().
				Str("labels", opts.Labels).
				Bool("eager_filter", true).
				Msg("Applying eager label filtering")
		}
	}
	
	if opts.Page > 0 {
		params.Set("page", strconv.Itoa(opts.Page))
	}
	if opts.Limit > 0 {
		params.Set("limit", strconv.Itoa(opts.Limit))
	}
	
	// FULL_RESCAN: Optionally ignore since parameter for complete rescan
	if !c.config.FullRescan && !opts.Since.IsZero() {
		params.Set("since", opts.Since.Format(time.RFC3339))
		if c.config.DebugURLs {
			log.Debug().
				Time("since", opts.Since).
				Msg("Using since parameter for incremental fetch")
		}
	} else if c.config.FullRescan {
		if c.config.DebugURLs {
			log.Debug().
				Bool("full_rescan", true).
				Msg("Performing full rescan (ignoring since parameter)")
		}
	}
	
	if len(params) > 0 {
		endpoint += "?" + params.Encode()
	}
	
	resp, err := c.makeRequest(ctx, "GET", endpoint)
	if err != nil {
		return nil, fmt.Errorf("failed to get issues: %w", err)
	}
	defer resp.Body.Close()
	
	var issues []Issue
	if err := json.NewDecoder(resp.Body).Decode(&issues); err != nil {
		return nil, fmt.Errorf("failed to decode issues: %w", err)
	}
	
	// Apply in-code filtering when EAGER_FILTER is disabled
	if !c.config.EagerFilter && opts.Labels != "" {
		issues = c.filterIssuesByLabels(issues, opts.Labels)
		if c.config.DebugURLs {
			log.Debug().
				Str("labels", opts.Labels).
				Bool("eager_filter", false).
				Int("filtered_count", len(issues)).
				Msg("Applied in-code label filtering")
		}
	}
	
	// Set repository information on each issue for context
	for i := range issues {
		issues[i].Repository = IssueRepository{
			Name:     repo,
			FullName: fmt.Sprintf("%s/%s", owner, repo),
			Owner:    owner, // Now a string instead of User object
		}
	}
	
	if c.config.DebugURLs {
		log.Debug().
			Str("owner", owner).
			Str("repo", repo).
			Int("issue_count", len(issues)).
			Msg("Gitea issues fetched successfully")
	}
	
	return issues, nil
}

// filterIssuesByLabels filters issues by label names (in-code filtering when eager filter is disabled)
func (c *Client) filterIssuesByLabels(issues []Issue, labelFilter string) []Issue {
	if labelFilter == "" {
		return issues
	}
	
	// Parse comma-separated label names
	requiredLabels := strings.Split(labelFilter, ",")
	for i, label := range requiredLabels {
		requiredLabels[i] = strings.TrimSpace(label)
	}
	
	var filtered []Issue
	for _, issue := range issues {
		hasRequiredLabels := true
		
		for _, requiredLabel := range requiredLabels {
			found := false
			for _, issueLabel := range issue.Labels {
				if issueLabel.Name == requiredLabel {
					found = true
					break
				}
			}
			if !found {
				hasRequiredLabels = false
				break
			}
		}
		
		if hasRequiredLabels {
			filtered = append(filtered, issue)
		}
	}
	
	return filtered
}

// GetIssue retrieves a specific issue
func (c *Client) GetIssue(ctx context.Context, owner, repo string, issueNumber int64) (*Issue, error) {
	endpoint := fmt.Sprintf("/repos/%s/%s/issues/%d", url.PathEscape(owner), url.PathEscape(repo), issueNumber)
	
	resp, err := c.makeRequest(ctx, "GET", endpoint)
	if err != nil {
		return nil, fmt.Errorf("failed to get issue: %w", err)
	}
	defer resp.Body.Close()
	
	var issue Issue
	if err := json.NewDecoder(resp.Body).Decode(&issue); err != nil {
		return nil, fmt.Errorf("failed to decode issue: %w", err)
	}
	
	// Set repository information
	issue.Repository = IssueRepository{
		Name:     repo,
		FullName: fmt.Sprintf("%s/%s", owner, repo),
		Owner:    owner, // Now a string instead of User object
	}
	
	return &issue, nil
}

// IssueListOptions contains options for listing issues
type IssueListOptions struct {
	State  string    // "open", "closed", "all"
	Labels string    // Comma-separated list of label names
	Page   int       // Page number (1-based)
	Limit  int       // Number of items per page (default: 20, max: 100)
	Since  time.Time // Only show issues updated after this time
}

// TestConnection tests the connection to Gitea API
func (c *Client) TestConnection(ctx context.Context) error {
	resp, err := c.makeRequest(ctx, "GET", "/user")
	if err != nil {
		return fmt.Errorf("connection test failed: %w", err)
	}
	defer resp.Body.Close()
	
	return nil
}

// WebhookPayload represents a Gitea webhook payload
type WebhookPayload struct {
	Action     string     `json:"action"`
	Number     int64      `json:"number,omitempty"`
	Issue      *Issue     `json:"issue,omitempty"`
	Repository Repository `json:"repository"`
	Sender     User       `json:"sender"`
}

// CreateLabelRequest represents the request to create a new label
type CreateLabelRequest struct {
	Name        string `json:"name"`
	Color       string `json:"color"`
	Description string `json:"description"`
}

// CreateLabel creates a new label in a repository
func (c *Client) CreateLabel(ctx context.Context, owner, repo string, label CreateLabelRequest) (*Label, error) {
	endpoint := fmt.Sprintf("/repos/%s/%s/labels", url.PathEscape(owner), url.PathEscape(repo))
	
	jsonData, err := json.Marshal(label)
	if err != nil {
		return nil, fmt.Errorf("failed to marshal label data: %w", err)
	}
	
	req, err := http.NewRequestWithContext(ctx, "POST", fmt.Sprintf("%s/api/v1%s", c.baseURL, endpoint), strings.NewReader(string(jsonData)))
	if err != nil {
		return nil, fmt.Errorf("failed to create request: %w", err)
	}
	
	if c.token != "" {
		req.Header.Set("Authorization", "token "+c.token)
	}
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Accept", "application/json")
	
	resp, err := c.client.Do(req)
	if err != nil {
		return nil, fmt.Errorf("failed to make request: %w", err)
	}
	defer resp.Body.Close()
	
	if resp.StatusCode >= 400 {
		return nil, fmt.Errorf("API request failed with status %d", resp.StatusCode)
	}
	
	var createdLabel Label
	if err := json.NewDecoder(resp.Body).Decode(&createdLabel); err != nil {
		return nil, fmt.Errorf("failed to decode label: %w", err)
	}
	
	return &createdLabel, nil
}

// GetLabels retrieves all labels from a repository
func (c *Client) GetLabels(ctx context.Context, owner, repo string) ([]Label, error) {
	endpoint := fmt.Sprintf("/repos/%s/%s/labels", url.PathEscape(owner), url.PathEscape(repo))
	
	resp, err := c.makeRequest(ctx, "GET", endpoint)
	if err != nil {
		return nil, fmt.Errorf("failed to get labels: %w", err)
	}
	defer resp.Body.Close()
	
	var labels []Label
	if err := json.NewDecoder(resp.Body).Decode(&labels); err != nil {
		return nil, fmt.Errorf("failed to decode labels: %w", err)
	}
	
	return labels, nil
}

// @goal: WHOOSH-LABELS-004, WSH-CONSISTENCY - Ecosystem standardization
// WHY: Ensures all CHORUS ecosystem repositories have consistent GitHub-standard labels
// EnsureRequiredLabels ensures that required labels exist in the repository
func (c *Client) EnsureRequiredLabels(ctx context.Context, owner, repo string) error {
	// @goal: WHOOSH-LABELS-004 - Standardized label set matching WHOOSH repository conventions
	// WHY: Provides consistent issue categorization across all CHORUS ecosystem repositories
	requiredLabels := []CreateLabelRequest{
		{
			Name:        "bug",
			Color:       "ee0701",
			Description: "Something is not working",
		},
		{
			Name:        "bzzz-task",
			Color:       "5319e7", // @goal: WHOOSH-LABELS-004 - Corrected color to match ecosystem standard
			Description: "CHORUS task for auto ingestion.",
		},
		{
			Name:        "chorus-entrypoint",
			Color:       "ff6b6b",
			Description: "Marks issues that trigger council formation for project kickoffs",
		},
		{
			Name:        "duplicate",
			Color:       "cccccc",
			Description: "This issue or pull request already exists",
		},
		{
			Name:        "enhancement",
			Color:       "84b6eb",
			Description: "New feature",
		},
		{
			Name:        "help wanted",
			Color:       "128a0c",
			Description: "Need some help",
		},
		{
			Name:        "invalid",
			Color:       "e6e6e6",
			Description: "Something is wrong",
		},
		{
			Name:        "question",
			Color:       "cc317c",
			Description: "More information is needed",
		},
		{
			Name:        "wontfix",
			Color:       "ffffff",
			Description: "This won't be fixed",
		},
	}
	
	// @goal: WHOOSH-LABELS-004 - Check existing labels to avoid duplicates
	// WHY: Prevents API errors from attempting to create labels that already exist
	existingLabels, err := c.GetLabels(ctx, owner, repo)
	if err != nil {
		return fmt.Errorf("failed to get existing labels: %w", err)
	}

	// @goal: WHOOSH-LABELS-004 - Build lookup map for efficient duplicate checking
	// WHY: O(1) lookup performance for label existence checking
	existingLabelNames := make(map[string]bool)
	for _, label := range existingLabels {
		existingLabelNames[label.Name] = true
	}

	// @goal: WHOOSH-LABELS-004 - Create only missing standardized labels
	// WHY: Ensures all repositories have consistent labeling without overwriting existing labels
	for _, requiredLabel := range requiredLabels {
		if !existingLabelNames[requiredLabel.Name] {
			_, err := c.CreateLabel(ctx, owner, repo, requiredLabel)
			if err != nil {
				return fmt.Errorf("failed to create label %s: %w", requiredLabel.Name, err)
			}
		}
	}
	
	return nil
}
