package grafanabackuper

import (
	"context"
	"encoding/json"
	"errors"
	"slices"

	"git.ar21.de/yolokube/grafana-backuper/internal/config"
	"git.ar21.de/yolokube/grafana-backuper/pkg/git"
	"git.ar21.de/yolokube/grafana-backuper/pkg/grafana"
)

var ErrAlreadyCommited = errors.New("already committed")

type BackupClient struct {
	client *Client
}

func (b *BackupClient) Start(ctx context.Context, cfg *config.Config) error {
	b.client.logger.Debug().Str("search-type", string(grafana.SearchTypeDashboard)).Msg("Searching dashboards by type")
	dashboards, _, err := b.client.grafana.File.Search(ctx, grafana.WithType(grafana.SearchTypeDashboard))
	if err != nil {
		return err
	}
	b.client.logger.Debug().
		Str("search-type", string(grafana.SearchTypeDashboard)).
		Int("counter", len(dashboards)).
		Msg("Found dashboards")

	for _, dashboardInfo := range dashboards {
		b.client.logger.Debug().
			Str("dashboard-uid", dashboardInfo.UID).
			Str("dashboard", dashboardInfo.Title).
			Msg("Fetching dashboard data")
		var dashboard *grafana.Dashboard
		dashboard, _, err = b.client.grafana.Dashboard.Get(ctx, dashboardInfo.UID)
		if err != nil {
			return err
		}

		b.client.logger.Debug().
			Str("dashboard-uid", dashboardInfo.UID).
			Str("dashboard", dashboardInfo.Title).
			Msg("Fetching dashboard versions")
		var versions []*grafana.DashboardVersion
		versions, _, err = b.client.grafana.DashboardVersion.List(ctx, dashboardInfo.UID)
		if err != nil {
			return err
		}
		b.client.logger.Debug().
			Str("dashboard-uid", dashboardInfo.UID).
			Str("dashboard", dashboardInfo.Title).
			Int("counter", len(versions)).
			Msg("Found dashboard versions")

		slices.Reverse(versions)

		uncommitedVersion := cfg.ForceCommits
		for _, version := range versions {
			b.client.logger.Debug().
				Str("dashboard-uid", dashboardInfo.UID).
				Uint("version", version.Version).
				Msg("Fetching version data")
			var dashboardVersion *grafana.DashboardVersion
			dashboardVersion, _, err = b.client.grafana.DashboardVersion.Get(ctx, dashboardInfo.UID, version.Version)
			if err != nil {
				return err
			}

			var commitmsg string
			commitmsg, err = b.commitDashboardVersion(
				dashboard,
				dashboardVersion,
				dashboardInfo,
				cfg,
				uncommitedVersion,
			)
			if errors.Is(err, ErrAlreadyCommited) {
				b.client.logger.Info().Str("commit-msg", commitmsg).Msg("Already committed")
				continue
			} else if err != nil {
				return err
			}

			uncommitedVersion = true
			b.client.logger.Info().Str("commit-msg", commitmsg).Msg("Commit created")
		}
	}

	if !b.client.git.HasChanges() {
		return nil
	}

	return b.client.git.Push(ctx)
}

func (b *BackupClient) commitDashboardVersion(
	dashboard *grafana.Dashboard,
	dashboardVersion *grafana.DashboardVersion,
	dashboardInfo *grafana.SearchResult,
	cfg *config.Config,
	force bool,
) (string, error) {
	commitmsg := Message{
		ID:    dashboardVersion.ID,
		Path:  dashboardInfo.Title,
		Title: dashboardVersion.Message,
		UID:   dashboardInfo.UID,
	}.String()

	b.client.logger.Debug().Str("commit", commitmsg).Msg("Checking commit existence")
	if !force && b.client.git.CommitExists(commitmsg) {
		return commitmsg, ErrAlreadyCommited
	}

	b.updateDashboardInfo(dashboard, dashboardVersion)

	b.client.logger.Debug().
		Str("folder", dashboard.FolderTitle).
		Str("dashboard-uid", dashboardVersion.DashboardUID).
		Msg("Marshalling the dashboard version data")
	data, err := json.MarshalIndent(grafana.SchemaFromDashboardMeta(dashboard), "", "  ")
	if err != nil {
		return commitmsg, err
	}

	commitOpts := []git.CommitOption{
		git.WithAuthor(dashboardVersion.CreatedBy, ""),
		git.WithFileContent(data, dashboardInfo.Title, dashboard.FolderTitle),
	}

	if b.client.signer != nil {
		commitOpts = append(commitOpts, git.WithSigner(*b.client.signer))
	}

	if cfg.GitUser != "" {
		commitOpts = append(commitOpts, git.WithCommitter(cfg.GitUser, cfg.GitEmail))
	}

	commit := b.client.git.NewCommit(commitOpts...)
	b.client.logger.Debug().Str("commit", commitmsg).Msg("Creating new commit")
	return commitmsg, commit.Create(commitmsg)
}

func (b *BackupClient) updateDashboardInfo(d *grafana.Dashboard, dv *grafana.DashboardVersion) {
	b.client.logger.Debug().
		Str("dashboard-uid", dv.DashboardUID).
		Str("folder", d.FolderTitle).
		Msg("Updating dashboard information")
	d.Dashboard = dv.Data
	d.UpdatedBy = dv.CreatedBy
	d.Updated = dv.Created
	d.Version = dv.Version
	b.client.logger.Debug().
		Str("dashboard-uid", dv.DashboardUID).
		Str("folder", d.FolderTitle).
		Msg("Updated dashboard information successfully")
}