package provider

import (
	"context"
	"fmt"
	"slices"

	"git.adyxax.org/adyxax/terraform-provider-forgejo/internal/client"
	"github.com/hashicorp/terraform-plugin-framework/datasource"
	"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
	"github.com/hashicorp/terraform-plugin-framework/types"
)

type TeamsDataSource struct {
	client *client.Client
}

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

type TeamsDataSourceModel struct {
	Elements         []TeamDataSourceModel `tfsdk:"elements"`
	OrganizationName types.String          `tfsdk:"organization_name"`
}

type TeamDataSourceModel struct {
	CanCreateOrgRepo        types.Bool   `tfsdk:"can_create_org_repo"`
	Description             types.String `tfsdk:"description"`
	Id                      types.Int64  `tfsdk:"id"`
	IncludesAllRepositories types.Bool   `tfsdk:"includes_all_repositories"`
	Name                    types.String `tfsdk:"name"`
	// Appears unused, the TeamsList function always returns nil
	//Organization            *OrganizationDataSourceModel  `tfsdk:"organization"`
	Permission types.String            `tfsdk:"permission"`
	Units      []types.String          `tfsdk:"units"`
	UnitsMap   map[string]types.String `tfsdk:"units_map"`
}

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

func (d *TeamsDataSource) 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 teams for an organization.",
				NestedObject: schema.NestedAttributeObject{
					Attributes: map[string]schema.Attribute{
						"can_create_org_repo": schema.BoolAttribute{
							Computed:            true,
							MarkdownDescription: "Whether members of this team can create repositories that will belong to the organization.",
						},
						"description": schema.StringAttribute{
							Computed:            true,
							MarkdownDescription: "A description string.",
						},
						"id": schema.Int64Attribute{
							Computed:            true,
							MarkdownDescription: "The identifier of the team.",
						},
						"includes_all_repositories": schema.BoolAttribute{
							Computed:            true,
							MarkdownDescription: "Whether members of this team can access all the repositories that belong to the organization.",
						},
						"name": schema.StringAttribute{
							Computed:            true,
							MarkdownDescription: "The team's name are a part of.",
						},
						"permission": schema.StringAttribute{
							Computed:            true,
							MarkdownDescription: "The members' permission level on the organization.",
						},
						"units": schema.ListAttribute{
							Computed:            true,
							ElementType:         types.StringType,
							MarkdownDescription: "The list of units permissions.",
						},
						"units_map": schema.MapAttribute{
							Computed:            true,
							ElementType:         types.StringType,
							MarkdownDescription: "The map of units permissions and their level.",
						},
					},
				},
			},
			"organization_name": schema.StringAttribute{
				MarkdownDescription: "The name of the organization the teams are a part of.",
				Required:            true,
			},
		},
		MarkdownDescription: "Use this data source to retrieve information about existing forgejo teams belonging to an organization.",
	}
}

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

func (d *TeamsDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
	var data TeamsDataSourceModel
	resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
	if resp.Diagnostics.HasError() {
		return
	}
	teams, err := d.client.TeamsList(ctx, data.OrganizationName.ValueString())
	if err != nil {
		resp.Diagnostics.AddError("ListTeams", fmt.Sprintf("failed to list teams: %s", err))
		return
	}
	teamsList := make([]TeamDataSourceModel, len(teams))
	for i, team := range teams {
		teamsList[i] = TeamDataSourceModel{
			CanCreateOrgRepo:        types.BoolValue(team.CanCreateOrgRepo),
			Description:             types.StringValue(team.Description),
			Id:                      types.Int64Value(team.Id),
			IncludesAllRepositories: types.BoolValue(team.IncludesAllRepositories),
			Name:                    types.StringValue(team.Name),
			Permission:              types.StringValue(team.Permission),
			Units:                   make([]types.String, len(team.Units)),
			UnitsMap:                make(map[string]types.String),
		}
		slices.Sort(team.Units)
		for j, unit := range team.Units {
			teamsList[i].Units[j] = types.StringValue(unit)
		}
		for unit, perm := range team.UnitsMap {
			teamsList[i].UnitsMap[unit] = types.StringValue(perm)
		}
	}
	data.Elements = teamsList
	resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}