package db

import (
	"database/sql"
	"errors"
	"fmt"
	"strings"
	"time"

	"github.com/bluesky-social/indigo/atproto/syntax"
	"tangled.org/core/appview/models"
)

// notification types that qualify for focus mode
var FocusEligibleTypes = []models.NotificationType{
	models.NotificationTypeIssueCreated,
	models.NotificationTypeIssueReopen,
	models.NotificationTypeIssueCommented,
	models.NotificationTypePullCreated,
	models.NotificationTypePullReopen,
	models.NotificationTypePullCommented,
	models.NotificationTypeUserMentioned,
}

func focusEligiblePlaceholders() (string, []any) {
	placeholders := make([]string, len(FocusEligibleTypes))
	args := make([]any, len(FocusEligibleTypes))
	for i, t := range FocusEligibleTypes {
		placeholders[i] = "?"
		args[i] = string(t)
	}
	return strings.Join(placeholders, ", "), args
}

// marks a user as currently focusing
func BeginFocus(e Execer, did string) error {
	_, err := e.Exec(`insert or replace into focusing (did) values (?)`, did)
	if err != nil {
		return fmt.Errorf("BeginFocus: %w", err)
	}
	return nil
}

// remove the focusing flag for a user
func EndFocus(e Execer, did string) error {
	_, err := e.Exec(`delete from focusing where did = ?`, did)
	if err != nil {
		return fmt.Errorf("EndFocus: %w", err)
	}
	return nil
}

// whether a user is currently in focus mode
func GetFocusStatus(e Execer, did string) (bool, error) {
	var exists bool
	err := e.QueryRow(`select exists(select 1 from focusing where did = ?)`, did).Scan(&exists)
	if errors.Is(err, sql.ErrNoRows) {
		return false, nil
	}
	if err != nil {
		return false, fmt.Errorf("GetFocusStatus: %w", err)
	}
	return exists, nil
}

// oldest unread focus-eligible notification for the user, with its related entity populated
//
// returns nil, nil when empty
func GetNextFocusItem(e Execer, did string) (*models.NotificationWithEntity, error) {
	placeholders, typeArgs := focusEligiblePlaceholders()

	query := fmt.Sprintf(`
		select
			n.id, n.recipient_did, n.actor_did, n.type, n.entity_type, n.entity_id,
			n.read, n.created, n.repo_id, n.issue_id, n.pull_id,
			r.id as r_id, r.did as r_did, r.rkey as r_rkey, r.name as r_name, r.description as r_description, r.website as r_website, r.topics as r_topics,
			i.id as i_id, i.did as i_did, i.issue_id as i_issue_id, i.title as i_title, i.open as i_open,
			p.id as p_id, p.owner_did as p_owner_did, p.pull_id as p_pull_id, p.title as p_title, p.state as p_state
		from notifications n
		left join repos r on n.repo_id = r.id
		left join issues i on n.issue_id = i.id
		left join pulls p on n.pull_id = p.id
		where n.recipient_did = ?
		  and n.read = 0
		  and n.type in (%s)
		order by n.created asc
		limit 1
	`, placeholders)

	args := append([]any{did}, typeArgs...)

	row := e.QueryRow(query, args...)

	var n models.Notification
	var typeStr string
	var createdStr string
	var repo models.Repo
	var issue models.Issue
	var pull models.Pull
	var rId, iId, pId sql.NullInt64
	var rDid, rRkey, rName, rDescription, rWebsite, rTopicStr sql.NullString
	var iDid sql.NullString
	var iIssueId sql.NullInt64
	var iTitle sql.NullString
	var iOpen sql.NullBool
	var pOwnerDid sql.NullString
	var pPullId sql.NullInt64
	var pTitle sql.NullString
	var pState sql.NullInt64

	err := row.Scan(
		&n.ID, &n.RecipientDid, &n.ActorDid, &typeStr, &n.EntityType, &n.EntityId,
		&n.Read, &createdStr, &n.RepoId, &n.IssueId, &n.PullId,
		&rId, &rDid, &rRkey, &rName, &rDescription, &rWebsite, &rTopicStr,
		&iId, &iDid, &iIssueId, &iTitle, &iOpen,
		&pId, &pOwnerDid, &pPullId, &pTitle, &pState,
	)
	if errors.Is(err, sql.ErrNoRows) {
		return nil, nil
	}
	if err != nil {
		return nil, fmt.Errorf("GetNextFocusItem: %w", err)
	}

	n.Type = models.NotificationType(typeStr)
	n.Created, err = time.Parse(time.RFC3339, createdStr)
	if err != nil {
		return nil, fmt.Errorf("GetNextFocusItem: parse created: %w", err)
	}

	entry := &models.NotificationWithEntity{Notification: &n}

	if rId.Valid {
		repo.Id = rId.Int64
		if rDid.Valid {
			repo.Did = rDid.String
		}
		if rRkey.Valid {
			repo.Rkey = rRkey.String
		}
		if rName.Valid {
			repo.Name = rName.String
		}
		if rDescription.Valid {
			repo.Description = rDescription.String
		}
		if rWebsite.Valid {
			repo.Website = rWebsite.String
		}
		if rTopicStr.Valid {
			repo.Topics = strings.Fields(rTopicStr.String)
		}
		entry.Repo = &repo
	}

	if iId.Valid {
		issue.Id = iId.Int64
		if iDid.Valid {
			issue.Did = iDid.String
		}
		if iIssueId.Valid {
			issue.IssueId = int(iIssueId.Int64)
		}
		if iTitle.Valid {
			issue.Title = iTitle.String
		}
		if iOpen.Valid {
			issue.Open = iOpen.Bool
		}
		entry.Issue = &issue
	}

	if pId.Valid {
		pull.ID = pId.Int64
		if pOwnerDid.Valid {
			pull.OwnerDid = syntax.DID(pOwnerDid.String)
		}
		if pPullId.Valid {
			pull.PullId = pPullId.Int64
		}
		if pTitle.Valid {
			pull.Title = pTitle.String
		}
		if pState.Valid {
			pull.State = models.PullState(pState.Int64)
		}
		entry.Pull = &pull
	}

	return entry, nil
}

// returns the number of unread focus-eligible notifications for a user (not sure if we need this?)
func CountFocusNotifs(e Execer, did string) (int64, error) {
	placeholders, typeArgs := focusEligiblePlaceholders()

	query := fmt.Sprintf(`
		select count(1)
		from notifications
		where recipient_did = ?
		  and read = 0
		  and type in (%s)
	`, placeholders)

	args := append([]any{did}, typeArgs...)

	var count int64
	err := e.QueryRow(query, args...).Scan(&count)
	if errors.Is(err, sql.ErrNoRows) {
		return 0, nil
	}
	if err != nil {
		return 0, fmt.Errorf("CountFocusNotifs: %w", err)
	}
	return count, nil
}
