package gitops

import (
	"log"
	"strings"
	"time"

	"xorm.io/xorm"

	gitdomain "github.com/forgeo/forgebucket/internal/domain/git"
	"github.com/forgeo/forgebucket/internal/events"
	"github.com/forgeo/forgebucket/internal/models"
)

// CheckDrift resolves the HEAD SHA of branch in the repo at repoPath and
// compares it against actualSHA. Returns the resolved HEAD SHA, whether drift
// exists, and any error.
func CheckDrift(repoPath, branch, actualSHA string) (desiredSHA string, drifted bool, err error) {
	sha, err := gitdomain.RevParse(repoPath, branch)
	if err != nil {
		return "", false, err
	}
	return sha, sha != actualSHA, nil
}

// refToBranch strips the refs/heads/ prefix from a full git ref.
// Returns "" for non-branch refs (tags, etc.).
func refToBranch(ref string) string {
	return strings.TrimPrefix(ref, "refs/heads/")
}

// handlePush is called on every push.received event. For each GitOpsConfig
// on the pushed repo whose branch matches, it runs a drift check.
func (c *Controller) handlePush(evt events.PushEvent) {
	pushedBranch := refToBranch(evt.Ref)
	if pushedBranch == "" {
		return // tag push or other non-branch ref — ignore
	}

	var cfgs []models.GitOpsConfig
	if err := c.db.Where("repo_id = ?", evt.RepoID).Find(&cfgs); err != nil {
		return
	}

	for _, cfg := range cfgs {
		if cfg.Branch != pushedBranch {
			continue
		}
		c.evaluateDrift(cfg, evt.After)
	}
}

// evaluateDrift compares desiredSHA against the config's ActualSHA and takes
// the appropriate action: record drift and optionally auto-sync.
func (c *Controller) evaluateDrift(cfg models.GitOpsConfig, desiredSHA string) {
	now := time.Now().UTC()
	cfg.LastCheckedAt = &now
	cfg.DesiredSHA = desiredSHA

	if desiredSHA == cfg.ActualSHA {
		// Already in sync.
		cfg.SyncStatus = "synced"
		c.db.ID(cfg.ID).Cols("sync_status", "desired_sha", "last_checked_at").Update(&cfg) //nolint:errcheck
		return
	}

	// Drift detected — record and publish.
	log.Printf("gitops: drift on env %d: desired=%s actual=%s", cfg.EnvID, desiredSHA[:7], sha7(cfg.ActualSHA))

	drift := &models.GitOpsDriftEvent{
		EnvID:      cfg.EnvID,
		RepoID:     cfg.RepoID,
		DesiredSHA: desiredSHA,
		ActualSHA:  cfg.ActualSHA,
		SyncStatus: "drifted",
		DetectedAt: now,
	}
	c.db.Insert(drift) //nolint:errcheck

	cfg.SyncStatus = "drifted"
	c.db.ID(cfg.ID).Cols("sync_status", "desired_sha", "last_checked_at").Update(&cfg) //nolint:errcheck

	// Look up env name for the event payload.
	var env models.Environment
	c.db.ID(cfg.EnvID).Get(&env) //nolint:errcheck

	c.bus.Publish(events.SubjectEnvironmentDriftDetected, events.DriftEvent{ //nolint:errcheck
		EnvID:      cfg.EnvID,
		EnvName:    env.Name,
		RepoID:     cfg.RepoID,
		DesiredSHA: desiredSHA,
		ActualSHA:  cfg.ActualSHA,
		At:         now,
	})

	if cfg.AutoSync {
		c.TriggerSync(cfg, desiredSHA)
	}
}

// periodicCheck runs on a ticker and re-evaluates drift for every GitOpsConfig
// whose SyncInterval has elapsed.
func (c *Controller) periodicCheck() {
	now := time.Now().UTC()

	var cfgs []models.GitOpsConfig
	if err := c.db.Where("sync_interval > 0").Find(&cfgs); err != nil {
		return
	}

	for _, cfg := range cfgs {
		elapsed := now.Unix() - lastChecked(cfg).Unix()
		if int(elapsed) < cfg.SyncInterval {
			continue
		}

		var repo models.Repository
		if found, _ := c.db.ID(cfg.RepoID).Get(&repo); !found {
			continue
		}

		desiredSHA, drifted, err := CheckDrift(repo.DiskPath, cfg.Branch, cfg.ActualSHA)
		if err != nil {
			log.Printf("gitops: periodic check env %d: %v", cfg.EnvID, err)
			now2 := time.Now().UTC()
			cfg.LastCheckedAt = &now2
			c.db.ID(cfg.ID).Cols("last_checked_at").Update(&cfg) //nolint:errcheck
			continue
		}

		if drifted {
			c.evaluateDrift(cfg, desiredSHA)
		} else {
			now2 := time.Now().UTC()
			cfg.LastCheckedAt = &now2
			c.db.ID(cfg.ID).Cols("last_checked_at").Update(&cfg) //nolint:errcheck
		}
	}
}

// markSynced resolves any open drift events for envID and updates the config.
func markSynced(db *xorm.Engine, envID int64, sha string) {
	now := time.Now().UTC()
	db.Where("env_id = ? AND resolved_at IS NULL", envID).
		Cols("sync_status", "resolved_at").
		Update(&models.GitOpsDriftEvent{SyncStatus: "synced", ResolvedAt: &now}) //nolint:errcheck

	db.Where("env_id = ?", envID).
		Cols("sync_status", "actual_sha", "last_checked_at").
		Update(&models.GitOpsConfig{SyncStatus: "synced", ActualSHA: sha, LastCheckedAt: &now}) //nolint:errcheck
}

func lastChecked(cfg models.GitOpsConfig) time.Time {
	if cfg.LastCheckedAt != nil {
		return *cfg.LastCheckedAt
	}
	return cfg.CreatedAt
}

func sha7(s string) string {
	if len(s) >= 7 {
		return s[:7]
	}
	if s == "" {
		return "(none)"
	}
	return s
}
