package store

import (
	"context"
	"fmt"
	"time"
)

type UserShow struct {
	ItemID   string    `json:"itemId"`
	Title    string    `json:"title"`
	Year     *int      `json:"year,omitempty"`
	ImageTag string    `json:"imageTag,omitempty"`
	AddedAt  time.Time `json:"addedAt"`
}

type NotificationPreferences struct {
	Enabled          bool `json:"enabled"`
	ShowReturnAlerts bool `json:"showReturnAlerts"`
	LeadDays         int  `json:"leadDays"`
}

type UserNotification struct {
	ID        int64      `json:"id"`
	Kind      string     `json:"kind"`
	ItemID    string     `json:"itemId,omitempty"`
	Title     string     `json:"title"`
	Message   string     `json:"message"`
	EventAt   *time.Time `json:"eventAt,omitempty"`
	CreatedAt time.Time  `json:"createdAt"`
	ReadAt    *time.Time `json:"readAt,omitempty"`
}

func (s *Store) SaveUserShow(ctx context.Context, userID string, show UserShow) error {
	_, err := s.pool.Exec(ctx, `
		INSERT INTO user_shows (emby_user_id, item_id, title, year, image_tag)
		VALUES ($1, $2, $3, $4, $5)
		ON CONFLICT (emby_user_id, item_id) DO UPDATE
		SET title = EXCLUDED.title, year = EXCLUDED.year, image_tag = EXCLUDED.image_tag`,
		userID, show.ItemID, show.Title, show.Year, show.ImageTag)
	return err
}

// SaveUserShowIfAbsent is the auto-follow path. Manual saves intentionally refresh
// metadata, while playback must only announce a show when it actually added it.
func (s *Store) SaveUserShowIfAbsent(ctx context.Context, userID string, show UserShow) (bool, error) {
	result, err := s.pool.Exec(ctx, `
		INSERT INTO user_shows (emby_user_id, item_id, title, year, image_tag)
		VALUES ($1, $2, $3, $4, $5)
		ON CONFLICT (emby_user_id, item_id) DO NOTHING`,
		userID, show.ItemID, show.Title, show.Year, show.ImageTag)
	if err != nil {
		return false, err
	}
	return result.RowsAffected() == 1, nil
}

func (s *Store) DeleteUserShow(ctx context.Context, userID, itemID string) error {
	_, err := s.pool.Exec(ctx, `
		WITH removed AS (
			DELETE FROM user_shows
			WHERE emby_user_id = $1 AND item_id = $2
			RETURNING item_id
		)
		UPDATE user_notifications SET dismissed_at = now()
		WHERE emby_user_id = $1
		  AND item_id IN (SELECT item_id FROM removed)
		  AND dismissed_at IS NULL`,
		userID, itemID)
	return err
}

func (s *Store) UserShows(ctx context.Context, userID string) ([]UserShow, error) {
	rows, err := s.pool.Query(ctx, `
		SELECT item_id, title, year, image_tag, added_at
		FROM user_shows WHERE emby_user_id = $1 ORDER BY added_at, lower(title)`, userID)
	if err != nil {
		return nil, fmt.Errorf("store: list user shows: %w", err)
	}
	defer rows.Close()
	shows := []UserShow{}
	for rows.Next() {
		var show UserShow
		if err := rows.Scan(&show.ItemID, &show.Title, &show.Year, &show.ImageTag, &show.AddedAt); err != nil {
			return nil, fmt.Errorf("store: scan user show: %w", err)
		}
		shows = append(shows, show)
	}
	return shows, rows.Err()
}

func (s *Store) NotificationPreferences(ctx context.Context, userID string) (NotificationPreferences, error) {
	prefs := NotificationPreferences{Enabled: true, ShowReturnAlerts: true, LeadDays: 7}
	err := s.pool.QueryRow(ctx, `
		SELECT enabled, show_return_alerts, lead_days
		FROM user_notification_preferences WHERE emby_user_id = $1`, userID).
		Scan(&prefs.Enabled, &prefs.ShowReturnAlerts, &prefs.LeadDays)
	if err != nil && !isNoRows(err) {
		return prefs, fmt.Errorf("store: notification preferences: %w", err)
	}
	return prefs, nil
}

func (s *Store) SetNotificationPreferences(
	ctx context.Context, userID string, prefs NotificationPreferences,
) error {
	if prefs.LeadDays < 1 {
		prefs.LeadDays = 1
	}
	if prefs.LeadDays > 30 {
		prefs.LeadDays = 30
	}
	_, err := s.pool.Exec(ctx, `
		INSERT INTO user_notification_preferences
			(emby_user_id, enabled, show_return_alerts, lead_days)
		VALUES ($1, $2, $3, $4)
		ON CONFLICT (emby_user_id) DO UPDATE SET
			enabled = EXCLUDED.enabled,
			show_return_alerts = EXCLUDED.show_return_alerts,
			lead_days = EXCLUDED.lead_days,
			updated_at = now()`,
		userID, prefs.Enabled, prefs.ShowReturnAlerts, prefs.LeadDays)
	return err
}

func (s *Store) UpsertNotification(
	ctx context.Context, userID, sourceKey, kind, itemID, title, message string, eventAt *time.Time,
) error {
	_, err := s.pool.Exec(ctx, `
		INSERT INTO user_notifications
			(emby_user_id, source_key, kind, item_id, title, message, event_at)
		VALUES ($1, $2, $3, $4, $5, $6, $7)
		ON CONFLICT (emby_user_id, source_key) DO NOTHING`,
		userID, sourceKey, kind, itemID, title, message, eventAt)
	return err
}

func (s *Store) UserNotifications(ctx context.Context, userID string) ([]UserNotification, error) {
	rows, err := s.pool.Query(ctx, `
		SELECT id, kind, item_id, title, message, event_at, created_at, read_at
		FROM user_notifications
		WHERE emby_user_id = $1 AND dismissed_at IS NULL
		ORDER BY created_at DESC LIMIT 100`, userID)
	if err != nil {
		return nil, fmt.Errorf("store: list notifications: %w", err)
	}
	defer rows.Close()
	notifications := []UserNotification{}
	for rows.Next() {
		var notification UserNotification
		if err := rows.Scan(
			&notification.ID, &notification.Kind, &notification.ItemID,
			&notification.Title, &notification.Message, &notification.EventAt,
			&notification.CreatedAt, &notification.ReadAt,
		); err != nil {
			return nil, fmt.Errorf("store: scan notification: %w", err)
		}
		notifications = append(notifications, notification)
	}
	return notifications, rows.Err()
}

func (s *Store) MarkNotificationRead(ctx context.Context, userID string, id int64) error {
	_, err := s.pool.Exec(ctx, `
		UPDATE user_notifications SET read_at = COALESCE(read_at, now())
		WHERE id = $1 AND emby_user_id = $2`, id, userID)
	return err
}

func (s *Store) DismissNotification(ctx context.Context, userID string, id int64) error {
	_, err := s.pool.Exec(ctx, `
		UPDATE user_notifications SET dismissed_at = now()
		WHERE id = $1 AND emby_user_id = $2`, id, userID)
	return err
}
