//go:build integration
// +build integration

// Copyright 2025 Cloudbase Solutions SRL
//
//	Licensed under the Apache License, Version 2.0 (the "License"); you may
//	not use this file except in compliance with the License. You may obtain
//	a copy of the License at
//
//	     http://www.apache.org/licenses/LICENSE-2.0
//
//	Unless required by applicable law or agreed to in writing, software
//	distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
//	WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
//	License for the specific language governing permissions and limitations
//	under the License.
package integration

import (
	"fmt"
	"time"

	"github.com/go-openapi/runtime"

	commonParams "github.com/cloudbase/garm-provider-common/params"
	"github.com/cloudbase/garm/client"
	clientInstances "github.com/cloudbase/garm/client/instances"
	"github.com/cloudbase/garm/params"
)

func (suite *GarmSuite) TestExternalProvider() {
	t := suite.T()
	t.Log("Testing external provider")
	repoPoolParams2 := params.CreatePoolParams{
		MaxRunners:     2,
		MinIdleRunners: 0,
		Flavor:         "default",
		Image:          "ubuntu:24.04",
		OSType:         commonParams.Linux,
		OSArch:         commonParams.Amd64,
		ProviderName:   "test_external",
		Tags:           []string{"repo-runner-2"},
		Enabled:        true,
	}
	repoPool2 := suite.CreateRepoPool(suite.repo.ID, repoPoolParams2)
	newParams := suite.UpdateRepoPool(suite.repo.ID, repoPool2.ID, repoPoolParams2.MaxRunners, 1)
	t.Logf("Updated repo pool with pool_id %s with new_params %+v", repoPool2.ID, newParams)

	err := suite.WaitPoolInstances(repoPool2.ID, commonParams.InstanceRunning, params.RunnerPending, 1*time.Minute)
	suite.NoError(err, "error waiting for pool instances to be running")
	repoPool2 = suite.GetRepoPool(suite.repo.ID, repoPool2.ID)
	suite.DisableRepoPool(suite.repo.ID, repoPool2.ID)
	suite.DeleteInstance(repoPool2.Instances[0].Name, false, false)
	err = suite.WaitPoolInstances(repoPool2.ID, commonParams.InstancePendingDelete, params.RunnerPending, 1*time.Minute)
	suite.NoError(err, "error waiting for pool instances to be pending delete")
	suite.DeleteInstance(repoPool2.Instances[0].Name, true, false) // delete instance with forceRemove
	err = suite.WaitInstanceToBeRemoved(repoPool2.Instances[0].Name, 1*time.Minute)
	suite.NoError(err, "error waiting for instance to be removed")
	suite.DeleteRepoPool(suite.repo.ID, repoPool2.ID)
}

func (suite *GarmSuite) WaitPoolInstances(poolID string, status commonParams.InstanceStatus, runnerStatus params.RunnerStatus, timeout time.Duration) error {
	t := suite.T()
	var timeWaited time.Duration // default is 0

	pool, err := getPool(suite.cli, suite.authToken, poolID)
	if err != nil {
		return err
	}

	t.Logf("Waiting for pool instances with pool_id %s to reach desired status %v and desired_runner_status %v", poolID, status, runnerStatus)
	for timeWaited < timeout {
		poolInstances, err := listPoolInstances(suite.cli, suite.authToken, poolID)
		if err != nil {
			return err
		}

		instancesCount := 0
		for _, instance := range poolInstances {
			if instance.Status == status && instance.RunnerStatus == runnerStatus {
				instancesCount++
			}
		}

		t.Logf(
			"Pool instance with pool_id %s reached status %v and runner_status %v, desired_instance_count %d, pool_instance_count %d",
			poolID, status, runnerStatus, instancesCount,
			len(poolInstances))
		if pool.MinIdleRunnersAsInt() == instancesCount {
			return nil
		}
		time.Sleep(5 * time.Second)
		timeWaited += 5 * time.Second
	}

	err = suite.dumpPoolInstancesDetails(pool.ID)
	suite.NoError(err, "error dumping pool instances details")

	return fmt.Errorf("timeout waiting for pool %s instances to reach status: %s and runner status: %s", poolID, status, runnerStatus)
}

func (suite *GarmSuite) dumpPoolInstancesDetails(poolID string) error {
	t := suite.T()
	pool, err := getPool(suite.cli, suite.authToken, poolID)
	if err != nil {
		return err
	}
	if err := printJSONResponse(pool); err != nil {
		return err
	}
	for _, instance := range pool.Instances {
		instanceDetails, err := getInstance(suite.cli, suite.authToken, instance.Name)
		if err != nil {
			return err
		}
		t.Logf("Instance details: instance_name %s", instance.Name)
		if err := printJSONResponse(instanceDetails); err != nil {
			return err
		}
	}
	return nil
}

func (suite *GarmSuite) DisableRepoPool(repoID, repoPoolID string) {
	t := suite.T()
	t.Logf("Disable repo pool with repo_id %s and pool_id %s", repoID, repoPoolID)
	enabled := false
	poolParams := params.UpdatePoolParams{Enabled: &enabled}
	_, err := updateRepoPool(suite.cli, suite.authToken, repoID, repoPoolID, poolParams)
	suite.NoError(err, "error disabling repository pool")
}

func (suite *GarmSuite) DeleteInstance(name string, forceRemove, bypassGHUnauthorized bool) {
	t := suite.T()
	t.Logf("Delete instance %s with force_remove %t", name, forceRemove)
	err := deleteInstance(suite.cli, suite.authToken, name, forceRemove, bypassGHUnauthorized)
	suite.NoError(err, "error deleting instance", name)
	t.Logf("Instance deletion initiated for instance %s", name)
}

func (suite *GarmSuite) WaitInstanceToBeRemoved(name string, timeout time.Duration) error {
	t := suite.T()
	var timeWaited time.Duration // default is 0
	var instance *params.Instance

	t.Logf("Waiting for instance %s to be removed", name)
	for timeWaited < timeout {
		instances, err := listInstances(suite.cli, suite.authToken)
		if err != nil {
			return err
		}

		instance = nil
		for k, v := range instances {
			if v.Name == name {
				instance = &instances[k]
				break
			}
		}
		if instance == nil {
			// The instance is not found in the list. We can safely assume
			// that it is removed
			return nil
		}

		time.Sleep(5 * time.Second)
		timeWaited += 5 * time.Second
	}

	if err := printJSONResponse(*instance); err != nil {
		return err
	}
	return fmt.Errorf("instance %s was not removed within the timeout", name)
}

func listPoolInstances(apiCli *client.GarmAPI, apiAuthToken runtime.ClientAuthInfoWriter, poolID string) (params.Instances, error) {
	listPoolInstancesResponse, err := apiCli.Instances.ListPoolInstances(
		clientInstances.NewListPoolInstancesParams().WithPoolID(poolID),
		apiAuthToken)
	if err != nil {
		return nil, err
	}
	return listPoolInstancesResponse.Payload, nil
}
