package services import ( "database/sql" "fmt" "time" "github.com/rede5/gohorsejobs/backend/internal/dto" "github.com/rede5/gohorsejobs/backend/internal/models" ) type VideoInterviewService struct { DB *sql.DB } func NewVideoInterviewService(db *sql.DB) *VideoInterviewService { return &VideoInterviewService{DB: db} } func (s *VideoInterviewService) CreateInterview(req dto.CreateVideoInterviewRequest, createdBy string) (*models.VideoInterview, error) { scheduledAt, err := time.Parse(time.RFC3339, req.ScheduledAt) if err != nil { return nil, fmt.Errorf("invalid scheduled_at format: %w", err) } if req.Timezone == "" { req.Timezone = "UTC" } if req.MeetingProvider == nil { provider := "custom" req.MeetingProvider = &provider } query := ` INSERT INTO video_interviews ( application_id, scheduled_at, duration_minutes, timezone, meeting_provider, notes, status, created_by, created_at, updated_at ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) RETURNING id, created_at, updated_at ` interview := &models.VideoInterview{ ApplicationID: req.ApplicationID, ScheduledAt: scheduledAt, DurationMinutes: req.DurationMinutes, Timezone: req.Timezone, MeetingProvider: req.MeetingProvider, Notes: req.Notes, Status: "scheduled", CreatedBy: &createdBy, CreatedAt: time.Now(), UpdatedAt: time.Now(), } err = s.DB.QueryRow( query, interview.ApplicationID, interview.ScheduledAt, interview.DurationMinutes, interview.Timezone, interview.MeetingProvider, interview.Notes, interview.Status, interview.CreatedBy, interview.CreatedAt, interview.UpdatedAt, ).Scan(&interview.ID, &interview.CreatedAt, &interview.UpdatedAt) if err != nil { return nil, fmt.Errorf("failed to create interview: %w", err) } return interview, nil } func (s *VideoInterviewService) GetInterviewByID(id string) (*models.VideoInterview, error) { query := ` SELECT id, application_id, scheduled_at, duration_minutes, timezone, meeting_link, meeting_provider, meeting_id, meeting_password, status, started_at, ended_at, notes, interviewer_feedback, candidate_feedback, rating, created_by, created_at, updated_at FROM video_interviews WHERE id = $1 ` interview := &models.VideoInterview{} err := s.DB.QueryRow(query, id).Scan( &interview.ID, &interview.ApplicationID, &interview.ScheduledAt, &interview.DurationMinutes, &interview.Timezone, &interview.MeetingLink, &interview.MeetingProvider, &interview.MeetingID, &interview.MeetingPassword, &interview.Status, &interview.StartedAt, &interview.EndedAt, &interview.Notes, &interview.InterviewerFeedback, &interview.CandidateFeedback, &interview.Rating, &interview.CreatedBy, &interview.CreatedAt, &interview.UpdatedAt, ) if err != nil { return nil, err } return interview, nil } func (s *VideoInterviewService) GetInterviewsByApplication(applicationID string) ([]models.VideoInterview, error) { query := ` SELECT id, application_id, scheduled_at, duration_minutes, timezone, meeting_link, meeting_provider, meeting_id, meeting_password, status, started_at, ended_at, notes, interviewer_feedback, candidate_feedback, rating, created_by, created_at, updated_at FROM video_interviews WHERE application_id = $1 ORDER BY scheduled_at DESC ` rows, err := s.DB.Query(query, applicationID) if err != nil { return nil, err } defer rows.Close() var interviews []models.VideoInterview for rows.Next() { var interview models.VideoInterview err := rows.Scan( &interview.ID, &interview.ApplicationID, &interview.ScheduledAt, &interview.DurationMinutes, &interview.Timezone, &interview.MeetingLink, &interview.MeetingProvider, &interview.MeetingID, &interview.MeetingPassword, &interview.Status, &interview.StartedAt, &interview.EndedAt, &interview.Notes, &interview.InterviewerFeedback, &interview.CandidateFeedback, &interview.Rating, &interview.CreatedBy, &interview.CreatedAt, &interview.UpdatedAt, ) if err != nil { return nil, err } interviews = append(interviews, interview) } return interviews, nil } func (s *VideoInterviewService) GetInterviewsByCompany(companyID string) ([]models.VideoInterviewWithDetails, error) { query := ` SELECT vi.id, vi.application_id, vi.scheduled_at, vi.duration_minutes, vi.timezone, vi.meeting_link, vi.meeting_provider, vi.meeting_id, vi.meeting_password, vi.status, vi.started_at, vi.ended_at, vi.notes, vi.interviewer_feedback, vi.candidate_feedback, vi.rating, vi.created_by, vi.created_at, vi.updated_at, j.title as job_title, c.id as company_id, c.name as company_name, COALESCE(u.name, a.name) as candidate_name, COALESCE(u.email, a.email) as candidate_email FROM video_interviews vi JOIN applications a ON vi.application_id = a.id JOIN jobs j ON a.job_id = j.id JOIN companies c ON j.company_id = c.id LEFT JOIN users u ON a.user_id = u.id WHERE c.id = $1 ORDER BY vi.scheduled_at DESC ` rows, err := s.DB.Query(query, companyID) if err != nil { return nil, err } defer rows.Close() var interviews []models.VideoInterviewWithDetails for rows.Next() { var interview models.VideoInterviewWithDetails err := rows.Scan( &interview.ID, &interview.ApplicationID, &interview.ScheduledAt, &interview.DurationMinutes, &interview.Timezone, &interview.MeetingLink, &interview.MeetingProvider, &interview.MeetingID, &interview.MeetingPassword, &interview.Status, &interview.StartedAt, &interview.EndedAt, &interview.Notes, &interview.InterviewerFeedback, &interview.CandidateFeedback, &interview.Rating, &interview.CreatedBy, &interview.CreatedAt, &interview.UpdatedAt, &interview.JobTitle, &interview.CompanyID, &interview.CompanyName, &interview.CandidateName, &interview.CandidateEmail, ) if err != nil { return nil, err } interviews = append(interviews, interview) } return interviews, nil } func (s *VideoInterviewService) UpdateInterview(id string, req dto.UpdateVideoInterviewRequest) (*models.VideoInterview, error) { interview, err := s.GetInterviewByID(id) if err != nil { return nil, err } if req.ScheduledAt != nil { scheduledAt, err := time.Parse(time.RFC3339, *req.ScheduledAt) if err != nil { return nil, fmt.Errorf("invalid scheduled_at format: %w", err) } interview.ScheduledAt = scheduledAt } if req.DurationMinutes != nil { interview.DurationMinutes = *req.DurationMinutes } if req.Timezone != nil { interview.Timezone = *req.Timezone } if req.MeetingLink != nil { interview.MeetingLink = req.MeetingLink } if req.MeetingProvider != nil { interview.MeetingProvider = req.MeetingProvider } if req.MeetingID != nil { interview.MeetingID = req.MeetingID } if req.MeetingPassword != nil { interview.MeetingPassword = req.MeetingPassword } if req.Status != nil { interview.Status = *req.Status if *req.Status == "in_progress" { now := time.Now() interview.StartedAt = &now } else if *req.Status == "completed" { now := time.Now() interview.EndedAt = &now } } if req.Notes != nil { interview.Notes = req.Notes } interview.UpdatedAt = time.Now() query := ` UPDATE video_interviews SET scheduled_at = $1, duration_minutes = $2, timezone = $3, meeting_link = $4, meeting_provider = $5, meeting_id = $6, meeting_password = $7, status = $8, started_at = $9, ended_at = $10, notes = $11, updated_at = $12 WHERE id = $13 ` _, err = s.DB.Exec(query, interview.ScheduledAt, interview.DurationMinutes, interview.Timezone, interview.MeetingLink, interview.MeetingProvider, interview.MeetingID, interview.MeetingPassword, interview.Status, interview.StartedAt, interview.EndedAt, interview.Notes, interview.UpdatedAt, id, ) if err != nil { return nil, fmt.Errorf("failed to update interview: %w", err) } return interview, nil } func (s *VideoInterviewService) SubmitFeedback(id string, req dto.VideoInterviewFeedbackRequest) (*models.VideoInterview, error) { interview, err := s.GetInterviewByID(id) if err != nil { return nil, err } if req.InterviewerFeedback != nil { interview.InterviewerFeedback = req.InterviewerFeedback } if req.CandidateFeedback != nil { interview.CandidateFeedback = req.CandidateFeedback } if req.Rating != nil { interview.Rating = req.Rating } interview.UpdatedAt = time.Now() query := ` UPDATE video_interviews SET interviewer_feedback = $1, candidate_feedback = $2, rating = $3, updated_at = $4 WHERE id = $5 ` _, err = s.DB.Exec(query, interview.InterviewerFeedback, interview.CandidateFeedback, interview.Rating, interview.UpdatedAt, id, ) if err != nil { return nil, fmt.Errorf("failed to submit feedback: %w", err) } return interview, nil } func (s *VideoInterviewService) DeleteInterview(id string) error { result, err := s.DB.Exec("DELETE FROM video_interviews WHERE id = $1", id) if err != nil { return err } rowsAffected, _ := result.RowsAffected() if rowsAffected == 0 { return sql.ErrNoRows } return nil }