package service

import (
	"context"
	"fmt"

	"github.com/grafana/grafana/pkg/bus"
	"github.com/grafana/grafana/pkg/infra/log"
	"github.com/grafana/grafana/pkg/models"
	"github.com/grafana/grafana/pkg/plugins"
	"github.com/grafana/grafana/pkg/services/dashboardimport"
	"github.com/grafana/grafana/pkg/services/dashboards"
	"github.com/grafana/grafana/pkg/services/plugindashboards"
	"github.com/grafana/grafana/pkg/services/pluginsettings"
)

func ProvideDashboardUpdater(bus bus.Bus, pluginStore plugins.Store, pluginDashboardService plugindashboards.Service,
	dashboardImportService dashboardimport.Service, pluginSettingsService pluginsettings.Service,
	dashboardPluginService dashboards.PluginService, dashboardService dashboards.DashboardService) *DashboardUpdater {
	du := newDashboardUpdater(bus, pluginStore, pluginDashboardService, dashboardImportService,
		pluginSettingsService, dashboardPluginService, dashboardService)
	du.updateAppDashboards()
	return du
}

func newDashboardUpdater(bus bus.Bus, pluginStore plugins.Store,
	pluginDashboardService plugindashboards.Service, dashboardImportService dashboardimport.Service,
	pluginSettingsService pluginsettings.Service, dashboardPluginService dashboards.PluginService,
	dashboardService dashboards.DashboardService) *DashboardUpdater {
	s := &DashboardUpdater{
		pluginStore:            pluginStore,
		pluginDashboardService: pluginDashboardService,
		dashboardImportService: dashboardImportService,
		pluginSettingsService:  pluginSettingsService,
		dashboardPluginService: dashboardPluginService,
		dashboardService:       dashboardService,
		logger:                 log.New("plugindashboards"),
	}
	bus.AddEventListener(s.handlePluginStateChanged)

	return s
}

type DashboardUpdater struct {
	pluginStore            plugins.Store
	pluginDashboardService plugindashboards.Service
	dashboardImportService dashboardimport.Service
	pluginSettingsService  pluginsettings.Service
	dashboardPluginService dashboards.PluginService
	dashboardService       dashboards.DashboardService
	logger                 log.Logger
}

func (du *DashboardUpdater) updateAppDashboards() {
	du.logger.Debug("Looking for app dashboard updates")

	pluginSettings, err := du.pluginSettingsService.GetPluginSettings(context.Background(), &pluginsettings.GetArgs{OrgID: 0})
	if err != nil {
		du.logger.Error("Failed to get all plugin settings", "error", err)
		return
	}

	for _, pluginSetting := range pluginSettings {
		// ignore disabled plugins
		if !pluginSetting.Enabled {
			continue
		}

		if pluginDef, exists := du.pluginStore.Plugin(context.Background(), pluginSetting.PluginID); exists {
			if pluginDef.Info.Version != pluginSetting.PluginVersion {
				du.syncPluginDashboards(context.Background(), pluginDef, pluginSetting.OrgID)
			}
		}
	}
}

func (du *DashboardUpdater) syncPluginDashboards(ctx context.Context, plugin plugins.PluginDTO, orgID int64) {
	du.logger.Info("Syncing plugin dashboards to DB", "pluginId", plugin.ID)

	// Get plugin dashboards
	req := &plugindashboards.ListPluginDashboardsRequest{
		OrgID:    orgID,
		PluginID: plugin.ID,
	}
	resp, err := du.pluginDashboardService.ListPluginDashboards(ctx, req)
	if err != nil {
		du.logger.Error("Failed to load app dashboards", "error", err)
		return
	}

	// Update dashboards with updated revisions
	for _, dash := range resp.Items {
		// remove removed ones
		if dash.Removed {
			du.logger.Info("Deleting plugin dashboard", "pluginId", plugin.ID, "dashboard", dash.Slug)

			if err := du.dashboardService.DeleteDashboard(ctx, dash.DashboardId, orgID); err != nil {
				du.logger.Error("Failed to auto update app dashboard", "pluginId", plugin.ID, "error", err)
				return
			}

			continue
		}

		// update updated ones
		if dash.ImportedRevision != dash.Revision {
			if err := du.autoUpdateAppDashboard(ctx, dash, orgID); err != nil {
				du.logger.Error("Failed to auto update app dashboard", "pluginId", plugin.ID, "error", err)
				return
			}
		}
	}

	// update version in plugin_setting table to mark that we have processed the update
	query := pluginsettings.GetByPluginIDArgs{PluginID: plugin.ID, OrgID: orgID}
	ps, err := du.pluginSettingsService.GetPluginSettingByPluginID(ctx, &query)
	if err != nil {
		du.logger.Error("Failed to read plugin setting by ID", "error", err)
		return
	}

	cmd := pluginsettings.UpdatePluginVersionArgs{
		OrgID:         ps.OrgID,
		PluginID:      ps.PluginID,
		PluginVersion: plugin.Info.Version,
	}

	if err := du.pluginSettingsService.UpdatePluginSettingPluginVersion(ctx, &cmd); err != nil {
		du.logger.Error("Failed to update plugin setting version", "error", err)
	}
}

func (du *DashboardUpdater) handlePluginStateChanged(ctx context.Context, event *models.PluginStateChangedEvent) error {
	du.logger.Info("Plugin state changed", "pluginId", event.PluginId, "enabled", event.Enabled)

	if event.Enabled {
		p, exists := du.pluginStore.Plugin(ctx, event.PluginId)
		if !exists {
			return fmt.Errorf("plugin %s not found. Could not sync plugin dashboards", event.PluginId)
		}

		du.syncPluginDashboards(ctx, p, event.OrgId)
	} else {
		query := models.GetDashboardsByPluginIdQuery{PluginId: event.PluginId, OrgId: event.OrgId}
		if err := du.dashboardPluginService.GetDashboardsByPluginID(ctx, &query); err != nil {
			return err
		}

		for _, dash := range query.Result {
			du.logger.Info("Deleting plugin dashboard", "pluginId", event.PluginId, "dashboard", dash.Slug)
			if err := du.dashboardService.DeleteDashboard(ctx, dash.Id, dash.OrgId); err != nil {
				return err
			}
		}
	}

	return nil
}

func (du *DashboardUpdater) autoUpdateAppDashboard(ctx context.Context, pluginDashInfo *plugindashboards.PluginDashboard, orgID int64) error {
	req := &plugindashboards.LoadPluginDashboardRequest{
		PluginID:  pluginDashInfo.PluginId,
		Reference: pluginDashInfo.Reference,
	}
	resp, err := du.pluginDashboardService.LoadPluginDashboard(ctx, req)
	if err != nil {
		return err
	}
	du.logger.Info("Auto updating App dashboard", "dashboard", resp.Dashboard.Title, "newRev",
		pluginDashInfo.Revision, "oldRev", pluginDashInfo.ImportedRevision)
	_, err = du.dashboardImportService.ImportDashboard(ctx, &dashboardimport.ImportDashboardRequest{
		PluginId:  pluginDashInfo.PluginId,
		User:      &models.SignedInUser{UserId: 0, OrgRole: models.ROLE_ADMIN, OrgId: orgID},
		Path:      pluginDashInfo.Reference,
		FolderId:  0,
		Dashboard: resp.Dashboard.Data,
		Overwrite: true,
		Inputs:    nil,
	})
	return err
}
