package registry

import (
	"fmt"

	packerSvc "github.com/hashicorp/hcp-sdk-go/clients/cloud-packer-service/preview/2021-04-30/client/packer_service"
	organizationSvc "github.com/hashicorp/hcp-sdk-go/clients/cloud-resource-manager/preview/2019-12-10/client/organization_service"
	projectSvc "github.com/hashicorp/hcp-sdk-go/clients/cloud-resource-manager/preview/2019-12-10/client/project_service"
	rmmodels "github.com/hashicorp/hcp-sdk-go/clients/cloud-resource-manager/preview/2019-12-10/models"
	"github.com/hashicorp/hcp-sdk-go/httpclient"
	"github.com/hashicorp/packer/internal/registry/env"
	"github.com/hashicorp/packer/version"
)

// Client is an HCP client capable of making requests on behalf of a service principal
type Client struct {
	Packer       packerSvc.ClientService
	Organization organizationSvc.ClientService
	Project      projectSvc.ClientService

	// OrganizationID  is the organization unique identifier on HCP.
	OrganizationID string

	// ProjectID  is the project unique identifier on HCP.
	ProjectID string
}

// NewClient returns an authenticated client to a HCP Packer Registry.
// Client authentication requires the following environment variables be set HCP_CLIENT_ID and HCP_CLIENT_SECRET.
// Upon error a HCPClientError will be returned.
func NewClient() (*Client, error) {
	if !env.HasHCPCredentials() {
		return nil, &ClientError{
			StatusCode: InvalidClientConfig,
			Err:        fmt.Errorf("the client authentication requires both %s and %s environment variables to be set", env.HCPClientID, env.HCPClientSecret),
		}
	}

	cl, err := httpclient.New(httpclient.Config{
		SourceChannel: fmt.Sprintf("packer/%s", version.PackerVersion.FormattedVersion()),
	})
	if err != nil {
		return nil, &ClientError{
			StatusCode: InvalidClientConfig,
			Err:        err,
		}
	}

	client := &Client{
		Packer:       packerSvc.New(cl, nil),
		Organization: organizationSvc.New(cl, nil),
		Project:      projectSvc.New(cl, nil),
	}

	if err := client.loadOrganizationID(); err != nil {
		return nil, &ClientError{
			StatusCode: InvalidClientConfig,
			Err:        err,
		}
	}
	if err := client.loadProjectID(); err != nil {
		return nil, &ClientError{
			StatusCode: InvalidClientConfig,
			Err:        err,
		}
	}

	return client, nil
}

func (c *Client) loadOrganizationID() error {
	// Get the organization ID.
	listOrgParams := organizationSvc.NewOrganizationServiceListParams()
	listOrgResp, err := c.Organization.OrganizationServiceList(listOrgParams, nil)
	if err != nil {
		return fmt.Errorf("unable to fetch organization list: %v", err)
	}
	orgLen := len(listOrgResp.Payload.Organizations)
	if orgLen != 1 {
		return fmt.Errorf("unexpected number of organizations: expected 1, actual: %v", orgLen)
	}
	c.OrganizationID = listOrgResp.Payload.Organizations[0].ID
	return nil
}

func (c *Client) loadProjectID() error {
	// Get the project using the organization ID.
	listProjParams := projectSvc.NewProjectServiceListParams()
	listProjParams.ScopeID = &c.OrganizationID
	scopeType := string(rmmodels.HashicorpCloudResourcemanagerResourceIDResourceTypeORGANIZATION)
	listProjParams.ScopeType = &scopeType
	listProjResp, err := c.Project.ProjectServiceList(listProjParams, nil)
	if err != nil {
		return fmt.Errorf("unable to fetch project id: %v", err)
	}
	if len(listProjResp.Payload.Projects) > 1 {
		return fmt.Errorf("this version of Packer does not support multiple projects")
	}
	c.ProjectID = listProjResp.Payload.Projects[0].ID
	return nil
}
