package services

import (
	"context"
	"database/sql"
	"errors"
	"fmt"

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

	"mgit.msbls.de/m/paliad/internal/models"
)

// AppointmentTargetService — CRUD on paliad.appointment_caldav_targets.
//
// Each row is the per-(appointment, binding) push state: the caldav_uid
// PUT into that binding's calendar (canonical per Appointment) plus the
// ETag returned by the server on the last successful PUT. Replaces the
// scalar paliad.appointments.caldav_uid / caldav_etag columns for the
// post-Slice-2a sync engine; those scalars stay populated for back-compat
// through Slice 4.
type AppointmentTargetService struct {
	db *sqlx.DB
}

func NewAppointmentTargetService(db *sqlx.DB) *AppointmentTargetService {
	return &AppointmentTargetService{db: db}
}

const targetColumns = `appointment_id, binding_id, caldav_uid, caldav_etag, last_pushed_at`

// UpsertAfterPush records the result of a successful PUT to the binding's
// calendar. Called by CalDAVService.pushAll after each PUT.
func (s *AppointmentTargetService) UpsertAfterPush(ctx context.Context, appointmentID, bindingID uuid.UUID, uid, etag string) error {
	var etagPtr *string
	if etag != "" {
		etagPtr = &etag
	}
	_, err := s.db.ExecContext(ctx,
		`INSERT INTO paliad.appointment_caldav_targets
		    (appointment_id, binding_id, caldav_uid, caldav_etag, last_pushed_at)
		 VALUES ($1, $2, $3, $4, NOW())
		 ON CONFLICT (appointment_id, binding_id)
		 DO UPDATE SET caldav_uid = EXCLUDED.caldav_uid,
		               caldav_etag = EXCLUDED.caldav_etag,
		               last_pushed_at = NOW()`,
		appointmentID, bindingID, uid, etagPtr)
	if err != nil {
		return fmt.Errorf("upsert caldav target: %w", err)
	}
	return nil
}

// FindByUIDAndBinding returns the target row matching this (uid, binding)
// pair, or nil when no such row exists.
func (s *AppointmentTargetService) FindByUIDAndBinding(ctx context.Context, uid string, bindingID uuid.UUID) (*models.AppointmentCalDAVTarget, error) {
	var t models.AppointmentCalDAVTarget
	err := s.db.GetContext(ctx, &t,
		`SELECT `+targetColumns+`
		   FROM paliad.appointment_caldav_targets
		  WHERE caldav_uid = $1 AND binding_id = $2`, uid, bindingID)
	if errors.Is(err, sql.ErrNoRows) {
		return nil, nil
	}
	if err != nil {
		return nil, fmt.Errorf("find target by uid+binding: %w", err)
	}
	return &t, nil
}

// ListForBinding returns every target row attached to this binding.
// Used by the pull-reconciliation pass to detect remote deletions.
func (s *AppointmentTargetService) ListForBinding(ctx context.Context, bindingID uuid.UUID) ([]models.AppointmentCalDAVTarget, error) {
	rows := []models.AppointmentCalDAVTarget{}
	if err := s.db.SelectContext(ctx, &rows,
		`SELECT `+targetColumns+`
		   FROM paliad.appointment_caldav_targets
		  WHERE binding_id = $1`, bindingID); err != nil {
		return nil, fmt.Errorf("list targets for binding: %w", err)
	}
	return rows, nil
}

// DeleteByAppointmentAndBinding removes one specific target row.
// Used after a successful remote DELETE.
func (s *AppointmentTargetService) DeleteByAppointmentAndBinding(ctx context.Context, appointmentID, bindingID uuid.UUID) error {
	_, err := s.db.ExecContext(ctx,
		`DELETE FROM paliad.appointment_caldav_targets
		  WHERE appointment_id = $1 AND binding_id = $2`, appointmentID, bindingID)
	if err != nil {
		return fmt.Errorf("delete target: %w", err)
	}
	return nil
}

// StaleForBinding returns target rows whose appointment_id is no longer
// in the in-scope set. Used by the post-pull cleanup pass to delete
// appointments that left the binding's scope (e.g. project unshared,
// scope_kind PATCHed). currentAppointmentIDs may be empty — in that
// case every target row is considered stale.
func (s *AppointmentTargetService) StaleForBinding(ctx context.Context, bindingID uuid.UUID, currentAppointmentIDs []uuid.UUID) ([]models.AppointmentCalDAVTarget, error) {
	rows := []models.AppointmentCalDAVTarget{}
	if len(currentAppointmentIDs) == 0 {
		if err := s.db.SelectContext(ctx, &rows,
			`SELECT `+targetColumns+`
			   FROM paliad.appointment_caldav_targets
			  WHERE binding_id = $1`, bindingID); err != nil {
			return nil, fmt.Errorf("stale-targets all: %w", err)
		}
		return rows, nil
	}
	query, args, err := sqlx.In(
		`SELECT `+targetColumns+`
		   FROM paliad.appointment_caldav_targets
		  WHERE binding_id = ?
		    AND appointment_id NOT IN (?)`, bindingID, currentAppointmentIDs)
	if err != nil {
		return nil, fmt.Errorf("stale-targets prepare: %w", err)
	}
	query = s.db.Rebind(query)
	if err := s.db.SelectContext(ctx, &rows, query, args...); err != nil {
		return nil, fmt.Errorf("stale-targets exec: %w", err)
	}
	return rows, nil
}
