// Pipeline-related methods for the Woodpecker CI adapter.
package woodpecker

import (
	"context"
	"fmt"
	"strings"
	"time"

	"go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker"

	"github.com/orchard9/rdev/internal/domain"
)

// ListPipelines returns recent CI pipeline executions for a repository.
func (c *Client) ListPipelines(ctx context.Context, owner, repo string) ([]*domain.CIPipeline, error) {
	select {
	case <-ctx.Done():
		return nil, ctx.Err()
	default:
	}

	fullName := owner + "/" + repo

	r, err := c.client.RepoLookup(fullName)
	if err != nil {
		return nil, fmt.Errorf("repo not found: %s", fullName)
	}

	pipelines, err := c.client.PipelineList(r.ID, woodpecker.PipelineListOptions{})
	if err != nil {
		return nil, fmt.Errorf("failed to list pipelines: %w", err)
	}

	result := make([]*domain.CIPipeline, len(pipelines))
	for i, p := range pipelines {
		result[i] = pipelineFromWoodpecker(p)
	}
	return result, nil
}

// GetPipeline returns a specific pipeline execution by number.
func (c *Client) GetPipeline(ctx context.Context, owner, repo string, number int64) (*domain.CIPipeline, error) {
	select {
	case <-ctx.Done():
		return nil, ctx.Err()
	default:
	}

	fullName := owner + "/" + repo

	r, err := c.client.RepoLookup(fullName)
	if err != nil {
		return nil, fmt.Errorf("repo not found: %s", fullName)
	}

	p, err := c.client.Pipeline(r.ID, number)
	if err != nil {
		return nil, fmt.Errorf("pipeline %d not found: %w", number, err)
	}

	return pipelineFromWoodpecker(p), nil
}

// GetPipelineSteps returns detailed step information for a pipeline.
// For failed steps, includes the last 50 lines of log output.
func (c *Client) GetPipelineSteps(ctx context.Context, owner, repo string, number int64) (*domain.CIPipelineSteps, error) {
	select {
	case <-ctx.Done():
		return nil, ctx.Err()
	default:
	}

	fullName := owner + "/" + repo

	r, err := c.client.RepoLookup(fullName)
	if err != nil {
		return nil, fmt.Errorf("repo not found: %s", fullName)
	}

	p, err := c.client.Pipeline(r.ID, number)
	if err != nil {
		return nil, fmt.Errorf("pipeline %d not found: %w", number, err)
	}

	result := &domain.CIPipelineSteps{
		PipelineNumber: number,
		URL:            fmt.Sprintf("%s/%s/%d", c.url, fullName, number),
		Steps:          make([]domain.CIPipelineStep, 0),
	}

	// Flatten workflows -> steps
	for _, wf := range p.Workflows {
		if wf == nil {
			continue
		}
		for _, step := range wf.Children {
			if step == nil {
				continue
			}

			// Calculate duration
			var duration int
			if step.Stopped > 0 && step.Started > 0 {
				duration = int(step.Stopped - step.Started)
			}

			// Convert exit code
			var exitCode *int
			if step.State == "success" || step.State == "failure" || step.State == "killed" {
				code := step.ExitCode
				exitCode = &code
			}

			domainStep := domain.CIPipelineStep{
				ID:       step.ID,
				Name:     step.Name,
				Status:   step.State,
				ExitCode: exitCode,
				Duration: duration,
				Error:    step.Error,
			}

			// Fetch logs for failed steps
			if step.State == "failure" || step.State == "error" || step.State == "killed" {
				logs, err := c.client.StepLogEntries(r.ID, number, step.ID)
				if err != nil {
					c.logger.Warn("failed to fetch step logs", "step", step.Name, "error", err)
				} else {
					domainStep.Log = formatLogEntries(logs, 50)
				}
			}

			result.Steps = append(result.Steps, domainStep)
		}
	}

	return result, nil
}

// TriggerBuild manually starts a new pipeline build on the specified branch.
func (c *Client) TriggerBuild(ctx context.Context, owner, repo, branch string) (int64, error) {
	select {
	case <-ctx.Done():
		return 0, ctx.Err()
	default:
	}

	fullName := owner + "/" + repo

	r, err := c.client.RepoLookup(fullName)
	if err != nil {
		return 0, fmt.Errorf("repo not found: %s", fullName)
	}

	// Create a new pipeline for the branch (with circuit breaker protection)
	var pipeline *woodpecker.Pipeline
	err = c.executeWithCircuitBreaker(func() error {
		var createErr error
		pipeline, createErr = c.client.PipelineCreate(r.ID, &woodpecker.PipelineOptions{
			Branch: branch,
		})
		return createErr
	})
	if err != nil {
		return 0, fmt.Errorf("failed to trigger build: %w", err)
	}

	c.logger.Info("build triggered", "repo", fullName, "branch", branch, "pipeline", pipeline.Number)
	return pipeline.Number, nil
}

// RetryPipeline restarts a failed or stopped pipeline.
func (c *Client) RetryPipeline(ctx context.Context, owner, repo string, number int64) (*domain.CIPipeline, error) {
	select {
	case <-ctx.Done():
		return nil, ctx.Err()
	default:
	}

	fullName := owner + "/" + repo

	r, err := c.client.RepoLookup(fullName)
	if err != nil {
		return nil, fmt.Errorf("repo not found: %s", fullName)
	}

	// Restart the pipeline using PipelineStart (with circuit breaker protection)
	var pipeline *woodpecker.Pipeline
	err = c.executeWithCircuitBreaker(func() error {
		var startErr error
		pipeline, startErr = c.client.PipelineStart(r.ID, number, woodpecker.PipelineStartOptions{})
		return startErr
	})
	if err != nil {
		return nil, fmt.Errorf("failed to retry pipeline %d: %w", number, err)
	}

	c.logger.Info("pipeline retried", "repo", fullName, "pipeline", number, "new_status", pipeline.Status)
	return pipelineFromWoodpecker(pipeline), nil
}

// pipelineFromWoodpecker converts a woodpecker.Pipeline to domain.CIPipeline.
func pipelineFromWoodpecker(p *woodpecker.Pipeline) *domain.CIPipeline {
	var started, finished time.Time
	if p.Started > 0 {
		started = time.Unix(p.Started, 0)
	}
	if p.Finished > 0 {
		finished = time.Unix(p.Finished, 0)
	}

	// Map pipeline errors
	var errors []domain.CIPipelineError
	for _, e := range p.Errors {
		if e != nil {
			errors = append(errors, domain.CIPipelineError{
				Type:      e.Type,
				Message:   e.Message,
				IsWarning: e.IsWarning,
			})
		}
	}

	return &domain.CIPipeline{
		ID:       p.ID,
		Number:   p.Number,
		Status:   p.Status,
		Event:    p.Event,
		Branch:   p.Branch,
		Commit:   p.Commit,
		Message:  p.Message,
		Author:   p.Author,
		Started:  started,
		Finished: finished,
		Errors:   errors,
	}
}

// formatLogEntries combines log entries and returns the last N lines.
func formatLogEntries(entries []*woodpecker.LogEntry, maxLines int) string {
	if len(entries) == 0 {
		return ""
	}

	// Collect all lines
	var lines []string
	for _, entry := range entries {
		if entry != nil && len(entry.Data) > 0 {
			// Data is []byte, convert to string and split
			data := string(entry.Data)
			entryLines := strings.Split(data, "\n")
			for _, line := range entryLines {
				if line != "" {
					lines = append(lines, line)
				}
			}
		}
	}

	// Return last maxLines
	if len(lines) > maxLines {
		lines = lines[len(lines)-maxLines:]
	}

	return strings.Join(lines, "\n")
}
