package persistence

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

func (p Profile) SaveNotification(n Notification) {
	tx, err := p.DB.Beginx()
	defer func() {
		if r := recover(); r != nil {
			err := tx.Rollback()
			if err != nil {
				panic(err)
			}
			panic(r) // Re-raise the panic
		}
	}()

	if err != nil {
		panic(err)
	}

	// Save the Notification
	_, err = tx.NamedExec(`
		insert into notifications(id, type, sent_at, sort_index, user_id, action_user_id, action_tweet_id, action_retweet_id,
			                      has_detail, last_scraped_at)
		     values (:id, :type, :sent_at, :sort_index, :user_id, nullif(:action_user_id, 0), nullif(:action_tweet_id, 0),
		            nullif(:action_retweet_id, 0), :has_detail, :last_scraped_at)
		         on conflict do update
		        set sent_at = max(sent_at, :sent_at),
		            sort_index = max(sort_index, :sort_index),
		            action_user_id = nullif(:action_user_id, 0),
		            action_tweet_id = nullif(:action_tweet_id, 0),
		            action_retweet_id = nullif(:action_retweet_id, 0),
		            has_detail = has_detail or :has_detail,
		            last_scraped_at = max(last_scraped_at, :last_scraped_at)
	`, n)
	if err != nil {
		fmt.Printf("failed to save notification %#v\n", n)
		panic(err)
	}

	// Save relevant users and tweets
	for _, u_id := range n.UserIDs {
		_, err = tx.Exec(`
			insert into notification_users(notification_id, user_id) values (?, ?) on conflict do nothing
		`, n.ID, u_id)
		if err != nil {
			panic(err)
		}
	}
	for _, t_id := range n.TweetIDs {
		_, err = tx.Exec(`
			insert into notification_tweets(notification_id, tweet_id) values (?, ?) on conflict do nothing
		`, n.ID, t_id)
		if err != nil {
			fmt.Printf("failed to save notification %#v\n", n)
			panic(err)
		}
	}
	for _, r_id := range n.RetweetIDs {
		_, err = tx.Exec(`
			insert into notification_retweets(notification_id, retweet_id) values (?, ?) on conflict do nothing
		`, n.ID, r_id)
		if err != nil {
			fmt.Printf("failed to save notification %#v\n", n)
			panic(err)
		}
	}
	err = tx.Commit()
	if err != nil {
		panic(err)
	}
}

func (p Profile) GetNotification(id NotificationID) Notification {
	var ret Notification
	err := p.DB.Get(&ret,
		`select id, type, sent_at, sort_index, user_id, ifnull(action_user_id, 0) action_user_id,
		        ifnull(action_tweet_id, 0) action_tweet_id, ifnull(action_retweet_id, 0) action_retweet_id, has_detail, last_scraped_at
		   from notifications where id = ?`,
		id)
	if err != nil {
		panic(err)
	}
	err = p.DB.Select(&ret.UserIDs, `select user_id from notification_users where notification_id = ?`, id)
	if err != nil {
		panic(err)
	}
	ret.RetweetIDs = []TweetID{} // If the query returns no rows, these will otherwise be uninitialized (nil)!
	ret.TweetIDs = []TweetID{}
	err = p.DB.Select(&ret.TweetIDs, `select tweet_id from notification_tweets where notification_id = ?`, id)
	if err != nil {
		panic(err)
	}
	err = p.DB.Select(&ret.RetweetIDs, `select retweet_id from notification_retweets where notification_id = ?`, id)
	if err != nil {
		panic(err)
	}
	return ret
}

func (p Profile) CheckNotificationScrapesNeeded(trove TweetTrove) []NotificationID {
	ret := []NotificationID{}
	for n_id, notification := range trove.Notifications {
		// If there's no detail page, skip
		if !notification.HasDetail {
			continue
		}

		// Check its last-scraped
		var last_scraped_at Timestamp
		err := p.DB.Get(&last_scraped_at, `select last_scraped_at from notifications where id = ?`, n_id)
		if errors.Is(err, sql.ErrNoRows) {
			// It's not scraped at all yet
			ret = append(ret, n_id)
			continue
		} else if err != nil {
			panic(err)
		}
		// If the latest scrape is not fresh (older than the notification sent-at time), add it
		if last_scraped_at.Time.Before(notification.SentAt.Time) {
			ret = append(ret, n_id)
		}
	}
	return ret
}

func (p Profile) GetUnreadNotificationsCount(u_id UserID, since_sort_index int64) int {
	var ret int
	err := p.DB.Get(&ret, `select count(*) from notifications where sort_index > ? and user_id = ?`, since_sort_index, u_id)
	if err != nil {
		panic(err)
	}
	return ret
}
