package provider

import (
	"context"
	"fmt"

	"git.adyxax.org/adyxax/terraform-provider-eventline/external/evcli"
	"github.com/exograd/eventline/pkg/ksuid"
	"github.com/hashicorp/terraform-plugin-framework/datasource"
	"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
	"github.com/hashicorp/terraform-plugin-framework/types"
)

type JobsDataSource struct {
	client *evcli.Client
}

var _ datasource.DataSource = &JobsDataSource{} // Ensure provider defined types fully satisfy framework interfaces
func NewJobsDataSource() datasource.DataSource {
	return &JobsDataSource{}
}

type JobsDataSourceModel struct {
	Elements  []JobDataSourceModel `tfsdk:"elements"`
	ProjectId types.String         `tfsdk:"project_id"`
}
type JobDataSourceModel struct {
	Disabled types.Bool             `tfsdk:"disabled"`
	Id       types.String           `tfsdk:"id"`
	Spec     JobSpecDataSourceModel `tfsdk:"spec"`
}
type JobSpecDataSourceModel struct {
	Concurrent  types.Bool                 `tfsdk:"concurrent"`
	Description types.String               `tfsdk:"description"`
	Environment types.Map                  `tfsdk:"environment"`
	Identities  types.Set                  `tfsdk:"identities"`
	Name        types.String               `tfsdk:"name"`
	Parameters  []ParameterDataSourceModel `tfsdk:"parameters"`
	Retention   types.Int64                `tfsdk:"retention"`
	Runner      *RunnerDataSourceModel     `tfsdk:"runner"`
	Trigger     *TriggerDataSourceModel    `tfsdk:"trigger"`
	Steps       []StepDataSourceModel      `tfsdk:"steps"`
}
type ParameterDataSourceModel struct {
	Description types.String `tfsdk:"description"`
	Environment types.String `tfsdk:"environment"`
	Name        types.String `tfsdk:"name"`
	Type        types.String `tfsdk:"type"`
	Values      types.List   `tfsdk:"values"`
}
type RunnerDataSourceModel struct {
	Name     types.String `tfsdk:"name"`
	Identity types.String `tfsdk:"identity"`
}
type StepDataSourceModel struct {
	Code    types.String                `tfsdk:"code"`
	Command *StepCommandDataSourceModel `tfsdk:"command"`
	Label   types.String                `tfsdk:"label"`
	Script  *StepScriptDataSourceModel  `tfsdk:"script"`
}
type StepCommandDataSourceModel struct {
	Arguments types.List   `tfsdk:"arguments"`
	Name      types.String `tfsdk:"name"`
}
type StepScriptDataSourceModel struct {
	Arguments types.List   `tfsdk:"arguments"`
	Content   types.String `tfsdk:"content"`
	Path      types.String `tfsdk:"path"`
}
type TriggerDataSourceModel struct {
	Event    types.String `tfsdk:"event"`
	Identity types.String `tfsdk:"identity"`
}

func (d *JobsDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
	resp.TypeName = req.ProviderTypeName + "_jobs"
}

func (d *JobsDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
	resp.Schema = schema.Schema{
		Attributes: map[string]schema.Attribute{
			"elements": schema.ListNestedAttribute{
				Computed:            true,
				MarkdownDescription: "The list of jobs.",
				NestedObject: schema.NestedAttributeObject{
					Attributes: map[string]schema.Attribute{
						"disabled": schema.BoolAttribute{
							Computed:            true,
							MarkdownDescription: "Whether the job is disabled or not.",
						},
						"id": schema.StringAttribute{
							Computed:            true,
							MarkdownDescription: "The identifier of the job.",
						},
						"spec": schema.SingleNestedAttribute{
							Attributes: map[string]schema.Attribute{
								"concurrent": schema.BoolAttribute{
									Computed:            true,
									MarkdownDescription: "Whether to allow concurrent executions for this job or not.",
								},
								"description": schema.StringAttribute{
									Computed:            true,
									MarkdownDescription: "A textual description of the job.",
								},
								"environment": schema.MapAttribute{
									ElementType:         types.StringType,
									Computed:            true,
									MarkdownDescription: "A set of environment variables mapping names to values to be defined during job execution.",
								},
								"identities": schema.SetAttribute{
									Computed:            true,
									ElementType:         types.StringType,
									MarkdownDescription: "Set of eventline identities names to inject during job execution.",
								},
								"name": schema.StringAttribute{
									Computed:            true,
									MarkdownDescription: "The name of the job.",
								},
								"parameters": schema.ListNestedAttribute{
									Computed: true,
									NestedObject: schema.NestedAttributeObject{
										Attributes: map[string]schema.Attribute{
											"description": schema.StringAttribute{
												Computed:            true,
												MarkdownDescription: "A textual description of the parameter.",
											},
											"environment": schema.StringAttribute{
												Computed:            true,
												MarkdownDescription: "The name of an environment variable to be used to inject the value of this parameter during execution.",
											},
											"name": schema.StringAttribute{
												Computed:            true,
												MarkdownDescription: "The name of the parameter.",
											},
											"type": schema.StringAttribute{
												Computed:            true,
												MarkdownDescription: "The type of the parameter. The following types are supported:\n  - number: Either an integer or an IEEE 754 double precision floating point value.\n  - integer: An integer.\n  - string: A character string.\n  - boolean: A boolean.",
											},
											"values": schema.ListAttribute{
												Computed:            true,
												ElementType:         types.StringType,
												MarkdownDescription: "For parameters of type string, the list of valid values.",
											},
										},
									},
								},
								"retention": schema.Int64Attribute{
									Computed:            true,
									MarkdownDescription: "The number of days after which past executions of this job will be deleted. This value override the global job_retention setting.",
								},
								"runner": schema.SingleNestedAttribute{
									Attributes: map[string]schema.Attribute{
										"name": schema.StringAttribute{
											Computed:            true,
											MarkdownDescription: "The name of the runner.",
										},
										"identity": schema.StringAttribute{
											Computed:            true,
											MarkdownDescription: "The name of an identity to use for runners which require authentication. For example the ssh runner needs an identity to initiate an ssh connection.",
										},
									},
									Computed:            true,
									MarkdownDescription: "The specification of the runner used to execute the job.",
								},
								"trigger": schema.SingleNestedAttribute{
									Attributes: map[string]schema.Attribute{
										"event": schema.StringAttribute{
											Computed:            true,
											MarkdownDescription: "The event to react to formatted as <connector>/<event>.",
										},
										"identity": schema.StringAttribute{
											Computed:            true,
											MarkdownDescription: "The name of an identity to use for events which require authentication. For example the github/push event needs an identity to create the GitHub hook used to listen to push events.",
										},
									},
									Computed:            true,
									MarkdownDescription: "The specification of a trigger indicating when to execute the job.",
								},
								"steps": schema.ListNestedAttribute{
									Computed:            true,
									MarkdownDescription: "A list of steps which will be executed sequentially.",
									NestedObject: schema.NestedAttributeObject{
										Attributes: map[string]schema.Attribute{
											"code": schema.StringAttribute{
												Computed:            true,
												MarkdownDescription: "The fragment of code to execute for this step.",
											},
											"command": schema.SingleNestedAttribute{
												Attributes: map[string]schema.Attribute{
													"arguments": schema.ListAttribute{
														Computed:            true,
														ElementType:         types.StringType,
														MarkdownDescription: "The list of arguments to pass to the command.",
													},
													"name": schema.StringAttribute{
														Computed:            true,
														MarkdownDescription: "The name of the command.",
													},
												},
												Computed:            true,
												MarkdownDescription: "The command to execute for this step.",
											},
											"label": schema.StringAttribute{
												Computed:            true,
												MarkdownDescription: "A short description of the step which will be displayed on the web interface.",
											},
											"script": schema.SingleNestedAttribute{
												Attributes: map[string]schema.Attribute{
													"arguments": schema.ListAttribute{
														Computed:            true,
														ElementType:         types.StringType,
														MarkdownDescription: "The list of arguments to pass to the script.",
													},
													"content": schema.StringAttribute{
														Computed:            true,
														MarkdownDescription: "The script file contents.",
													},
													"path": schema.StringAttribute{
														Computed:            true,
														MarkdownDescription: "The path of the script file relative to the job file.",
													},
												},
												Computed:            true,
												MarkdownDescription: "The command to execute for this step.",
											},
										},
									},
								},
							},
							Computed:            true,
							MarkdownDescription: "The specification of the job.",
						},
					},
				},
			},
			"project_id": schema.StringAttribute{
				MarkdownDescription: "The identifier of the project the jobs are part of.",
				Required:            true,
			},
		},
		MarkdownDescription: "Use this data source to retrieve information about existing eventline jobs.",
	}
}

func (d *JobsDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
	d.client, _ = req.ProviderData.(*evcli.Client)
}

func (d *JobsDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
	var data JobsDataSourceModel
	resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
	if resp.Diagnostics.HasError() {
		return
	}
	var id ksuid.KSUID
	if err := id.Parse(data.ProjectId.ValueString()); err != nil {
		resp.Diagnostics.AddError("KsuidParse", fmt.Sprintf("Unable to parse project id, got error: %s", err))
		return
	}
	d.client.ProjectId = &id
	jobs, err := d.client.FetchJobs()
	if err != nil {
		resp.Diagnostics.AddError("FetchJobs", fmt.Sprintf("Unable to fetch jobs, got error: %s", err))
		return
	}
	jobList := make([]JobDataSourceModel, len(jobs))
	for i, job := range jobs {
		environment, _ := types.MapValueFrom(ctx, types.StringType, job.Spec.Environment)
		identities, _ := types.SetValueFrom(ctx, types.StringType, job.Spec.Identities)
		jobList[i] = JobDataSourceModel{
			Disabled: types.BoolValue(job.Disabled),
			Id:       types.StringValue(job.Id.String()),
			Spec: JobSpecDataSourceModel{
				Concurrent:  types.BoolValue(job.Spec.Concurrent),
				Description: types.StringValue(job.Spec.Description),
				Environment: environment,
				Identities:  identities,
				Name:        types.StringValue(job.Spec.Name),
				Retention:   types.Int64Value(int64(job.Spec.Retention)),
			},
		}
		jobParameters := make([]ParameterDataSourceModel, len(job.Spec.Parameters))
		for j, parameter := range job.Spec.Parameters {
			values, _ := types.ListValueFrom(ctx, types.StringType, parameter.Values)
			jobParameters[j] = ParameterDataSourceModel{
				Description: types.StringValue(parameter.Description),
				Environment: types.StringValue(parameter.Environment),
				Name:        types.StringValue(parameter.Name),
				Type:        types.StringValue(string(parameter.Type)),
				Values:      values,
			}
		}
		jobList[i].Spec.Parameters = jobParameters
		if job.Spec.Runner != nil {
			jobList[i].Spec.Runner = &RunnerDataSourceModel{
				Name:     types.StringValue(job.Spec.Runner.Name),
				Identity: types.StringValue(job.Spec.Runner.Identity),
			}
		}
		jobSteps := make([]StepDataSourceModel, len(job.Spec.Steps))
		for j, step := range job.Spec.Steps {
			jobSteps[j] = StepDataSourceModel{
				Code:  types.StringValue(step.Code),
				Label: types.StringValue(step.Label),
			}
			if step.Command != nil {
				arguments, _ := types.ListValueFrom(ctx, types.StringType, step.Command.Arguments)
				jobSteps[j].Command = &StepCommandDataSourceModel{
					Arguments: arguments,
					Name:      types.StringValue(step.Command.Name),
				}
			}
			if step.Script != nil {
				arguments, _ := types.ListValueFrom(ctx, types.StringType, step.Script.Arguments)
				jobSteps[j].Script = &StepScriptDataSourceModel{
					Arguments: arguments,
					Content:   types.StringValue(step.Script.Content),
					Path:      types.StringValue(step.Script.Path),
				}
			}
		}
		jobList[i].Spec.Steps = jobSteps
		if job.Spec.Trigger != nil {
			jobList[i].Spec.Trigger = &TriggerDataSourceModel{
				Event:    types.StringValue(job.Spec.Trigger.Event.String()),
				Identity: types.StringValue(job.Spec.Trigger.Identity),
			}
		}
	}
	data.Elements = jobList
	resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}