package cache

import (
	"context"
	"database/sql"
	"time"
)

func (s *SQLiteStore) UpsertPRReviewComment(ctx context.Context, comment PRReviewComment) error {
	tx, err := s.db.BeginTx(ctx, nil)
	if err != nil {
		return err
	}
	defer txRollbackOnError(tx, &err)
	if err := upsertPRReviewCommentTx(ctx, tx, comment); err != nil {
		return err
	}
	return tx.Commit()
}

func upsertPRReviewCommentTx(ctx context.Context, tx *sql.Tx, comment PRReviewComment) error {
	createdAt := comment.CreatedAt
	updatedAt := comment.UpdatedAt
	if createdAt.IsZero() {
		createdAt = time.Unix(0, 0).UTC()
	}
	if updatedAt.IsZero() {
		updatedAt = createdAt
	}
	return execTx(ctx, tx, `INSERT INTO pr_review_comments (repo_id, source_id, pr_number, comment_id, discussion_id, review_kind, author, path, line, start_line, end_line, position, original_position, resolved, resolvable, parent_id, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(repo_id, source_id) DO UPDATE SET pr_number = excluded.pr_number, comment_id = excluded.comment_id, discussion_id = excluded.discussion_id, review_kind = excluded.review_kind, author = excluded.author, path = excluded.path, line = excluded.line, start_line = excluded.start_line, end_line = excluded.end_line, position = excluded.position, original_position = excluded.original_position, resolved = excluded.resolved, resolvable = excluded.resolvable, parent_id = excluded.parent_id, updated_at = excluded.updated_at`,
		comment.RepoID, comment.SourceID, comment.PRNumber, comment.CommentID, comment.DiscussionID, comment.ReviewKind, comment.Author, comment.Path, comment.Line, comment.StartLine, comment.EndLine, comment.Position, comment.OriginalPosition, encodeNullableBool(comment.Resolved), encodeNullableBool(comment.Resolvable), comment.ParentID, createdAt.Format(time.RFC3339Nano), updatedAt.Format(time.RFC3339Nano))
}

func (s *SQLiteStore) ListPRReviewComments(ctx context.Context, filter PRReviewCommentFilter) ([]PRReviewComment, error) {
	query := `SELECT repo_id, source_id, pr_number, comment_id, discussion_id, review_kind, author, path, line, start_line, end_line, position, original_position, resolved, resolvable, parent_id, created_at, updated_at FROM pr_review_comments WHERE (? = '' OR repo_id = ?) AND (? = 0 OR pr_number = ?) AND (? = '' OR source_id = ?) ORDER BY pr_number, created_at, comment_id`
	rows, err := s.db.QueryContext(ctx, query, filter.RepoID, filter.RepoID, filter.PRNumber, filter.PRNumber, filter.SourceID, filter.SourceID)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	out := []PRReviewComment{}
	for rows.Next() {
		var comment PRReviewComment
		var resolvedRaw, resolvableRaw, createdRaw, updatedRaw string
		if err := rows.Scan(&comment.RepoID, &comment.SourceID, &comment.PRNumber, &comment.CommentID, &comment.DiscussionID, &comment.ReviewKind, &comment.Author, &comment.Path, &comment.Line, &comment.StartLine, &comment.EndLine, &comment.Position, &comment.OriginalPosition, &resolvedRaw, &resolvableRaw, &comment.ParentID, &createdRaw, &updatedRaw); err != nil {
			return nil, err
		}
		comment.Resolved = decodeNullableBool(resolvedRaw)
		comment.Resolvable = decodeNullableBool(resolvableRaw)
		comment.CreatedAt, _ = time.Parse(time.RFC3339Nano, createdRaw)
		comment.UpdatedAt, _ = time.Parse(time.RFC3339Nano, updatedRaw)
		out = append(out, comment)
	}
	return out, rows.Err()
}

func encodeNullableBool(value *bool) string {
	if value == nil {
		return ""
	}
	if *value {
		return "true"
	}
	return "false"
}

func decodeNullableBool(value string) *bool {
	switch value {
	case "true":
		v := true
		return &v
	case "false":
		v := false
		return &v
	default:
		return nil
	}
}