package buildkite

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

	"github.com/buildkite/buildkite-mcp-server/pkg/trace"
	"github.com/buildkite/go-buildkite/v4"
	"github.com/mark3labs/mcp-go/mcp"
	"go.opentelemetry.io/otel/attribute"
)

type PipelinesClient interface {
	Get(ctx context.Context, org, pipelineSlug string) (buildkite.Pipeline, *buildkite.Response, error)
	List(ctx context.Context, org string, options *buildkite.PipelineListOptions) ([]buildkite.Pipeline, *buildkite.Response, error)
	Create(ctx context.Context, org string, p buildkite.CreatePipeline) (buildkite.Pipeline, *buildkite.Response, error)
	Update(ctx context.Context, org, pipelineSlug string, p buildkite.UpdatePipeline) (buildkite.Pipeline, *buildkite.Response, error)
	AddWebhook(ctx context.Context, org, slug string) (*buildkite.Response, error)
}

type ListPipelinesArgs struct {
	OrgSlug     string `json:"org_slug"`
	Name        string `json:"name"`
	Repository  string `json:"repository"`
	Page        int    `json:"page"`
	PerPage     int    `json:"per_page"`
	DetailLevel string `json:"detail_level"` // "summary", "detailed", "full"
}

type CreatePipelineResult struct {
	Pipeline buildkite.Pipeline `json:"pipeline"`
	Webhook  *WebhookInfo       `json:"webhook,omitempty"`
}

type WebhookInfo struct {
	Created bool   `json:"created"`
	Error   string `json:"error,omitempty"`
	Note    string `json:"note,omitempty"`
}

func ListPipelines(client PipelinesClient) (tool mcp.Tool, handler mcp.TypedToolHandlerFunc[ListPipelinesArgs], scopes []string) {
	return mcp.NewTool("list_pipelines",
			mcp.WithDescription("List all pipelines in an organization with their basic details, build counts, and current status"),
			mcp.WithString("org_slug",
				mcp.Required(),
			),
			mcp.WithString("name",
				mcp.Description("Filter pipelines by name"),
			),
			mcp.WithString("repository",
				mcp.Description("Filter pipelines by repository URL"),
			),
			mcp.WithString("detail_level",
				mcp.Description("Response detail level: 'summary' (default), 'detailed', or 'full'"),
			),
			withPagination(),
			mcp.WithToolAnnotation(mcp.ToolAnnotation{
				Title:        "List Pipelines",
				ReadOnlyHint: mcp.ToBoolPtr(true),
			}),
		), func(ctx context.Context, request mcp.CallToolRequest, args ListPipelinesArgs) (*mcp.CallToolResult, error) {
			ctx, span := trace.Start(ctx, "buildkite.ListPipelines")
			defer span.End()

			if args.OrgSlug == "" {
				return mcp.NewToolResultError("org_slug is required"), nil
			}

			// Set defaults
			if args.DetailLevel == "" {
				args.DetailLevel = "summary"
			}
			if args.Page == 0 {
				args.Page = 1
			}
			if args.PerPage == 0 {
				args.PerPage = 30
			}

			span.SetAttributes(
				attribute.String("org_slug", args.OrgSlug),
				attribute.String("name_filter", args.Name),
				attribute.String("repository_filter", args.Repository),
				attribute.String("detail_level", args.DetailLevel),
				attribute.Int("page", args.Page),
				attribute.Int("per_page", args.PerPage),
			)

			pipelines, resp, err := client.List(ctx, args.OrgSlug, &buildkite.PipelineListOptions{
				ListOptions: buildkite.ListOptions{
					Page:    args.Page,
					PerPage: args.PerPage,
				},
				Name:       args.Name,
				Repository: args.Repository,
			})
			if err != nil {
				var errResp *buildkite.ErrorResponse
				if errors.As(err, &errResp) {
					if errResp.RawBody != nil {
						return mcp.NewToolResultError(string(errResp.RawBody)), nil
					}
				}

				return mcp.NewToolResultError(err.Error()), nil
			}

			headers := map[string]string{"Link": resp.Header.Get("Link")}

			var result any
			switch args.DetailLevel {
			case "summary":
				result = createPaginatedResult(pipelines, summarizePipeline, headers)
			case "detailed":
				result = createPaginatedResult(pipelines, detailPipeline, headers)
			default: // "full"
				result = createPaginatedResult(pipelines, func(p buildkite.Pipeline) buildkite.Pipeline { return p }, headers)
			}

			span.SetAttributes(
				attribute.Int("item_count", len(pipelines)),
			)

			return mcpTextResult(span, &result)
		}, []string{"read_pipelines"}
}

type GetPipelineArgs struct {
	OrgSlug      string `json:"org_slug"`
	PipelineSlug string `json:"pipeline_slug"`
	DetailLevel  string `json:"detail_level"` // "summary", "detailed", "full"
}

func GetPipeline(client PipelinesClient) (tool mcp.Tool, handler mcp.TypedToolHandlerFunc[GetPipelineArgs], scopes []string) {
	return mcp.NewTool("get_pipeline",
			mcp.WithDescription("Get detailed information about a specific pipeline including its configuration, steps, environment variables, and build statistics"),
			mcp.WithString("org_slug",
				mcp.Required(),
			),
			mcp.WithString("pipeline_slug",
				mcp.Required(),
			),
			mcp.WithString("detail_level",
				mcp.Description("Response detail level: 'summary', 'detailed', or 'full' (default)"),
			),
			mcp.WithToolAnnotation(mcp.ToolAnnotation{
				Title:        "Get Pipeline",
				ReadOnlyHint: mcp.ToBoolPtr(true),
			}),
		),
		func(ctx context.Context, request mcp.CallToolRequest, args GetPipelineArgs) (*mcp.CallToolResult, error) {
			ctx, span := trace.Start(ctx, "buildkite.GetPipeline")
			defer span.End()

			if args.OrgSlug == "" {
				return mcp.NewToolResultError("org_slug is required"), nil
			}
			if args.PipelineSlug == "" {
				return mcp.NewToolResultError("pipeline_slug is required"), nil
			}

			// Set default
			if args.DetailLevel == "" {
				args.DetailLevel = "full"
			}

			span.SetAttributes(
				attribute.String("org_slug", args.OrgSlug),
				attribute.String("pipeline_slug", args.PipelineSlug),
				attribute.String("detail_level", args.DetailLevel),
			)

			pipeline, _, err := client.Get(ctx, args.OrgSlug, args.PipelineSlug)
			if err != nil {
				var errResp *buildkite.ErrorResponse
				if errors.As(err, &errResp) {
					if errResp.RawBody != nil {
						return mcp.NewToolResultError(string(errResp.RawBody)), nil
					}
				}

				return mcp.NewToolResultError(err.Error()), nil
			}

			var result any
			switch args.DetailLevel {
			case "summary":
				result = summarizePipeline(pipeline)
			case "detailed":
				result = detailPipeline(pipeline)
			default: // "full"
				result = pipeline
			}

			return mcpTextResult(span, &result)
		}, []string{"read_pipelines"}
}

// PipelineSummary contains essential pipeline fields for token-efficient responses
type PipelineSummary struct {
	ID            string               `json:"id"`
	Name          string               `json:"name"`
	Slug          string               `json:"slug"`
	Repository    string               `json:"repository"`
	DefaultBranch string               `json:"default_branch"`
	WebURL        string               `json:"web_url"`
	Visibility    string               `json:"visibility"`
	CreatedAt     *buildkite.Timestamp `json:"created_at"`
	ArchivedAt    *buildkite.Timestamp `json:"archived_at,omitempty"`
}

// PipelineDetail contains pipeline fields excluding heavy configuration data
type PipelineDetail struct {
	ID                        string               `json:"id"`
	Name                      string               `json:"name"`
	Slug                      string               `json:"slug"`
	Repository                string               `json:"repository"`
	WebURL                    string               `json:"web_url"`
	DefaultBranch             string               `json:"default_branch"`
	Description               string               `json:"description"`
	ClusterID                 string               `json:"cluster_id"`
	Visibility                string               `json:"visibility"`
	Tags                      []string             `json:"tags"`
	SkipQueuedBranchBuilds    bool                 `json:"skip_queued_branch_builds"`
	CancelRunningBranchBuilds bool                 `json:"cancel_running_branch_builds"`
	StepsCount                int                  `json:"steps_count"`
	CreatedAt                 *buildkite.Timestamp `json:"created_at"`
	ArchivedAt                *buildkite.Timestamp `json:"archived_at,omitempty"`
}

// summarizePipeline converts a full Pipeline to PipelineSummary
func summarizePipeline(p buildkite.Pipeline) PipelineSummary {
	return PipelineSummary{
		ID:            p.ID,
		Name:          p.Name,
		Slug:          p.Slug,
		Repository:    p.Repository,
		DefaultBranch: p.DefaultBranch,
		WebURL:        p.WebURL,
		Visibility:    p.Visibility,
		CreatedAt:     p.CreatedAt,
		ArchivedAt:    p.ArchivedAt,
	}
}

// detailPipeline converts a full Pipeline to PipelineDetail
func detailPipeline(p buildkite.Pipeline) PipelineDetail {
	stepsCount := 0
	if p.Steps != nil {
		stepsCount = len(p.Steps)
	}

	return PipelineDetail{
		ID:                        p.ID,
		Name:                      p.Name,
		Slug:                      p.Slug,
		Repository:                p.Repository,
		WebURL:                    p.WebURL,
		DefaultBranch:             p.DefaultBranch,
		Description:               p.Description,
		ClusterID:                 p.ClusterID,
		Visibility:                p.Visibility,
		Tags:                      p.Tags,
		SkipQueuedBranchBuilds:    p.SkipQueuedBranchBuilds,
		CancelRunningBranchBuilds: p.CancelRunningBranchBuilds,
		StepsCount:                stepsCount,
		CreatedAt:                 p.CreatedAt,
		ArchivedAt:                p.ArchivedAt,
	}
}

// createPaginatedResult is a generic helper to convert pipelines and wrap in paginated result
func createPaginatedResult[T any](pipelines []buildkite.Pipeline, converter func(buildkite.Pipeline) T, headers map[string]string) PaginatedResult[T] {
	items := make([]T, len(pipelines))
	for i, p := range pipelines {
		items[i] = converter(p)
	}
	return PaginatedResult[T]{
		Items:   items,
		Headers: headers,
	}
}

type CreatePipelineArgs struct {
	OrgSlug                   string   `json:"org_slug"`
	Name                      string   `json:"name"`
	RepositoryURL             string   `json:"repository_url"`
	ClusterID                 string   `json:"cluster_id"`
	Description               string   `json:"description"`
	Configuration             string   `json:"configuration"`
	DefaultBranch             string   `json:"default_branch"`
	SkipQueuedBranchBuilds    bool     `json:"skip_queued_branch_builds"`
	CancelRunningBranchBuilds bool     `json:"cancel_running_branch_builds"`
	Tags                      []string `json:"tags"`
	CreateWebhook             bool     `json:"create_webhook"`
}

func CreatePipeline(client PipelinesClient) (tool mcp.Tool, handler mcp.TypedToolHandlerFunc[CreatePipelineArgs], scopes []string) {
	return mcp.NewTool("create_pipeline",
			mcp.WithDescription("Set up a new CI/CD pipeline in Buildkite with YAML configuration, repository connection, and cluster assignment"),
			mcp.WithString("org_slug",
				mcp.Required(),
				mcp.Description("The organization slug"),
			),
			mcp.WithString("name",
				mcp.Required(),
			),
			mcp.WithString("repository_url",
				mcp.Required(),
			),
			mcp.WithString("cluster_id",
				mcp.Required(),
			),
			mcp.WithString("configuration",
				mcp.Required(),
				mcp.Description("The pipeline configuration in YAML format. Contains the build steps and pipeline settings. If not provided, a basic configuration will be used"),
			),
			mcp.WithBoolean("create_webhook",
				mcp.Required(),
				mcp.Description("Create a GitHub webhook to trigger builds in response to pull-request and push events"),
				mcp.DefaultBool(true),
			),
			mcp.WithString("description"),
			mcp.WithString("default_branch",
				mcp.Description("The default branch for builds and metrics filtering"),
			),
			mcp.WithBoolean("skip_queued_branch_builds",
				mcp.Description("Skip intermediate builds when new builds are created on the same branch"),
			),
			mcp.WithBoolean("cancel_running_branch_builds",
				mcp.Description("Cancel running builds when new builds are created on the same branch"),
			),
			mcp.WithArray("tags",
				mcp.Description("Tags to apply to the pipeline. These can be used for filtering and organization"),
				mcp.Items(map[string]any{
					"type":        "string",
					"description": "A tag to apply to the pipeline",
				}),
			),
			mcp.WithToolAnnotation(mcp.ToolAnnotation{
				Title:        "Create Pipeline",
				ReadOnlyHint: mcp.ToBoolPtr(false),
			}),
		),
		func(ctx context.Context, request mcp.CallToolRequest, args CreatePipelineArgs) (*mcp.CallToolResult, error) {
			ctx, span := trace.Start(ctx, "buildkite.CreatePipeline")
			defer span.End()

			if args.OrgSlug == "" {
				return mcp.NewToolResultError("org_slug is required"), nil
			}
			if args.Name == "" {
				return mcp.NewToolResultError("name is required"), nil
			}
			if args.RepositoryURL == "" {
				return mcp.NewToolResultError("repository_url is required"), nil
			}
			if args.ClusterID == "" {
				return mcp.NewToolResultError("cluster_id is required"), nil
			}
			if args.Configuration == "" {
				return mcp.NewToolResultError("configuration is required"), nil
			}

			// parse the URL to ensure it's valid
			if _, err := url.Parse(args.RepositoryURL); err != nil {
				return mcp.NewToolResultError(fmt.Sprintf("invalid repository URL: %s", err.Error())), nil
			}

			span.SetAttributes(
				attribute.String("name", args.Name),
				attribute.String("repository_url", args.RepositoryURL),
				attribute.Bool("create_webhook", args.CreateWebhook),
			)

			create := buildkite.CreatePipeline{
				Name:                      args.Name,
				Repository:                args.RepositoryURL,
				ClusterID:                 args.ClusterID,
				Description:               args.Description,
				CancelRunningBranchBuilds: args.CancelRunningBranchBuilds,
				SkipQueuedBranchBuilds:    args.SkipQueuedBranchBuilds,
				Configuration:             args.Configuration,
				Tags:                      args.Tags,
			}

			if args.DefaultBranch != "" {
				create.DefaultBranch = args.DefaultBranch
			}

			pipeline, _, err := client.Create(ctx, args.OrgSlug, create)
			if err != nil {
				var errResp *buildkite.ErrorResponse
				if errors.As(err, &errResp) {
					if errResp.RawBody != nil {
						return mcp.NewToolResultError(string(errResp.RawBody)), nil
					}
				}

				return mcp.NewToolResultError(err.Error()), nil
			}

			if args.CreateWebhook {
				_, err := client.AddWebhook(ctx, args.OrgSlug, pipeline.Slug)
				result := CreatePipelineResult{
					Pipeline: pipeline,
					Webhook: &WebhookInfo{
						Created: err == nil,
						Note:    "Pipeline and webhook created successfully.",
					},
				}

				if err != nil {
					result.Webhook.Error = err.Error()
					result.Webhook.Note = "Pipeline created successfully, but webhook creation failed."
				}

				return mcpTextResult(span, &result)
			}

			result := CreatePipelineResult{
				Pipeline: pipeline,
			}
			return mcpTextResult(span, &result)
		}, []string{"write_pipelines"}
}

type UpdatePipelineArgs struct {
	OrgSlug                   string   `json:"org_slug"`
	PipelineSlug              string   `json:"pipeline_slug"`
	Name                      string   `json:"name"`
	RepositoryURL             string   `json:"repository_url"`
	ClusterID                 string   `json:"cluster_id"`
	Description               string   `json:"description"`
	Configuration             string   `json:"configuration"`
	DefaultBranch             string   `json:"default_branch"`
	SkipQueuedBranchBuilds    bool     `json:"skip_queued_branch_builds"`
	CancelRunningBranchBuilds bool     `json:"cancel_running_branch_builds"`
	Tags                      []string `json:"tags"` // Optional, labels to apply to the pipeline
}

func UpdatePipeline(client PipelinesClient) (mcp.Tool, mcp.TypedToolHandlerFunc[UpdatePipelineArgs], []string) {
	return mcp.NewTool("update_pipeline",
			mcp.WithDescription("Modify an existing Buildkite pipeline's configuration, repository, settings, or metadata"),
			mcp.WithString("org_slug",
				mcp.Required(),
			),
			mcp.WithString("pipeline_slug",
				mcp.Required(),
			),
			mcp.WithString("name"),
			mcp.WithString("repository_url",
				mcp.Description("The Git repository URL to use for the pipeline"),
			),
			mcp.WithString("cluster_id"),
			mcp.WithString("configuration",
				mcp.Description("The pipeline configuration in YAML format. Contains the build steps and pipeline settings. If not provided, the existing configuration will be used"),
			),
			mcp.WithString("description"),
			mcp.WithString("default_branch",
				mcp.Description("The default branch for builds and metrics filtering"),
			),
			mcp.WithBoolean("skip_queued_branch_builds",
				mcp.Description("Skip intermediate builds when new builds are created on the same branch"),
			),
			mcp.WithBoolean("cancel_running_branch_builds",
				mcp.Description("Cancel running builds when new builds are created on the same branch"),
			),
			mcp.WithArray("tags",
				mcp.Description("Tags to apply to the pipeline. These can be used for filtering and organization"),
				mcp.Items(map[string]any{
					"type":        "string",
					"description": "A tag to apply to the pipeline",
				}),
			),
			mcp.WithToolAnnotation(mcp.ToolAnnotation{
				Title:        "Update Pipeline",
				ReadOnlyHint: mcp.ToBoolPtr(false),
			}),
		), func(ctx context.Context, request mcp.CallToolRequest, args UpdatePipelineArgs) (*mcp.CallToolResult, error) {
			ctx, span := trace.Start(ctx, "buildkite.UpdatePipeline")
			defer span.End()

			if args.OrgSlug == "" {
				return mcp.NewToolResultError("org_slug is required"), nil
			}
			if args.RepositoryURL == "" {
				return mcp.NewToolResultError("repository_url is required"), nil
			}

			if args.PipelineSlug == "" {
				return mcp.NewToolResultError("pipeline_slug is required"), nil
			}
			if args.Configuration == "" {
				return mcp.NewToolResultError("configuration is required"), nil
			}

			// parse the URL to ensure it's valid
			if _, err := url.Parse(args.RepositoryURL); err != nil {
				return mcp.NewToolResultError(fmt.Sprintf("invalid repository URL: %s", err.Error())), nil
			}

			span.SetAttributes(
				attribute.String("org_slug", args.OrgSlug),
				attribute.String("pipeline_slug", args.PipelineSlug),
				attribute.String("repository_url", args.RepositoryURL),
			)

			update := buildkite.UpdatePipeline{
				Name:                      args.Name,
				Repository:                args.RepositoryURL,
				ClusterID:                 args.ClusterID,
				Description:               args.Description,
				CancelRunningBranchBuilds: args.CancelRunningBranchBuilds,
				SkipQueuedBranchBuilds:    args.SkipQueuedBranchBuilds,
				Configuration:             args.Configuration,
				Tags:                      args.Tags,
			}
			if args.DefaultBranch != "" {
				update.DefaultBranch = args.DefaultBranch
			}

			pipeline, _, err := client.Update(ctx, args.OrgSlug, args.PipelineSlug, update)
			if err != nil {
				var errResp *buildkite.ErrorResponse
				if errors.As(err, &errResp) {
					if errResp.RawBody != nil {
						return mcp.NewToolResultError(string(errResp.RawBody)), nil
					}
				}

				return mcp.NewToolResultError(err.Error()), nil
			}

			return mcpTextResult(span, &pipeline)
		}, []string{"write_pipelines"}
}
