package services

import (
	"context"
	"fmt"
	"strings"
	"time"

	"github.com/google/uuid"
	"github.com/jmoiron/sqlx"
)

// ChecklistPromotionService implements the global_admin-only promote /
// demote flow for paliad.checklists. Promote flips visibility to
// 'global' and stamps promoted_at / promoted_by; demote flips it back
// to a caller-chosen target ('firm' default — preserves visibility for
// already-instantiated users).
type ChecklistPromotionService struct {
	db        *sqlx.DB
	templates *ChecklistTemplateService
	audit     *SystemAuditLogService
	users     *UserService
}

func NewChecklistPromotionService(db *sqlx.DB, templates *ChecklistTemplateService, audit *SystemAuditLogService, users *UserService) *ChecklistPromotionService {
	return &ChecklistPromotionService{db: db, templates: templates, audit: audit, users: users}
}

// validDemoteTargets — narrowing the visibility back from 'global' is
// only allowed to a state where the row is still meaningful. 'global'
// would be a no-op; 'shared' would orphan existing instance owners who
// already see it without a grant. Default is 'firm'.
var validDemoteTargets = map[string]bool{"firm": true, "private": true}

// Promote flips an authored template to visibility='global'. Caller
// must be global_admin. Emits 'checklist.promoted_global' audit event
// with the prior visibility captured for the demote-undo path.
func (s *ChecklistPromotionService) Promote(ctx context.Context, callerID uuid.UUID, slug string) error {
	if err := s.requireGlobalAdmin(ctx, callerID); err != nil {
		return err
	}
	row, err := s.templates.GetBySlug(ctx, callerID, slug)
	if err != nil {
		return err
	}
	if row.Visibility == "global" {
		return nil
	}

	tx, err := s.db.BeginTxx(ctx, nil)
	if err != nil {
		return fmt.Errorf("begin promote tx: %w", err)
	}
	defer tx.Rollback()

	if _, err := tx.ExecContext(ctx,
		`UPDATE paliad.checklists
		    SET visibility = 'global',
		        promoted_at = $2,
		        promoted_by = $3,
		        updated_at  = $2
		  WHERE id = $1`, row.ID, time.Now().UTC(), callerID); err != nil {
		return fmt.Errorf("promote checklist: %w", err)
	}

	actorEmail, _ := s.actorEmail(ctx, callerID)
	if err := s.audit.WriteChecklistEvent(ctx, tx, ChecklistAuditEvent{
		EventType:  "checklist.promoted_global",
		ActorID:    callerID,
		ActorEmail: actorEmail,
		Metadata: map[string]any{
			"checklist_id":      row.ID,
			"slug":              slug,
			"owner_id":          row.OwnerID,
			"prior_visibility":  row.Visibility,
		},
	}); err != nil {
		return err
	}
	return tx.Commit()
}

// Demote narrows visibility from 'global' to target. target defaults to
// 'firm' when empty. promoted_at / promoted_by are cleared.
func (s *ChecklistPromotionService) Demote(ctx context.Context, callerID uuid.UUID, slug, target string) error {
	if err := s.requireGlobalAdmin(ctx, callerID); err != nil {
		return err
	}
	row, err := s.templates.GetBySlug(ctx, callerID, slug)
	if err != nil {
		return err
	}
	t := strings.ToLower(strings.TrimSpace(target))
	if t == "" {
		t = "firm"
	}
	if !validDemoteTargets[t] {
		return fmt.Errorf("%w: demote target must be firm | private, got %q", ErrInvalidInput, target)
	}
	if row.Visibility != "global" {
		return fmt.Errorf("%w: checklist is not currently promoted (visibility=%s)", ErrInvalidInput, row.Visibility)
	}

	tx, err := s.db.BeginTxx(ctx, nil)
	if err != nil {
		return fmt.Errorf("begin demote tx: %w", err)
	}
	defer tx.Rollback()

	if _, err := tx.ExecContext(ctx,
		`UPDATE paliad.checklists
		    SET visibility = $2,
		        promoted_at = NULL,
		        promoted_by = NULL,
		        updated_at  = now()
		  WHERE id = $1`, row.ID, t); err != nil {
		return fmt.Errorf("demote checklist: %w", err)
	}

	actorEmail, _ := s.actorEmail(ctx, callerID)
	if err := s.audit.WriteChecklistEvent(ctx, tx, ChecklistAuditEvent{
		EventType:  "checklist.demoted",
		ActorID:    callerID,
		ActorEmail: actorEmail,
		Metadata: map[string]any{
			"checklist_id":       row.ID,
			"slug":               slug,
			"target_visibility":  t,
		},
	}); err != nil {
		return err
	}
	return tx.Commit()
}

func (s *ChecklistPromotionService) requireGlobalAdmin(ctx context.Context, callerID uuid.UUID) error {
	user, err := s.users.GetByID(ctx, callerID)
	if err != nil {
		return err
	}
	if user == nil || user.GlobalRole != "global_admin" {
		return fmt.Errorf("%w: only global_admin can promote / demote checklists", ErrForbidden)
	}
	return nil
}

func (s *ChecklistPromotionService) actorEmail(ctx context.Context, userID uuid.UUID) (string, error) {
	u, err := s.users.GetByID(ctx, userID)
	if err != nil || u == nil {
		return "", err
	}
	return u.Email, nil
}
