using Microsoft.Extensions.Logging; using RealCV.Application.Interfaces; using RealCV.Application.Models; using RealCV.Infrastructure.Clients; namespace RealCV.Infrastructure.Services; public sealed class GitHubVerifierService : IGitHubVerifierService { private readonly GitHubApiClient _gitHubClient; private readonly ILogger _logger; // Map common skill names to GitHub languages private static readonly Dictionary SkillToLanguageMap = new(StringComparer.OrdinalIgnoreCase) { ["JavaScript"] = ["JavaScript", "TypeScript"], ["TypeScript"] = ["TypeScript"], ["Python"] = ["Python"], ["Java"] = ["Java", "Kotlin"], ["C#"] = ["C#"], [".NET"] = ["C#", "F#"], ["React"] = ["JavaScript", "TypeScript"], ["Angular"] = ["TypeScript", "JavaScript"], ["Vue"] = ["JavaScript", "TypeScript", "Vue"], ["Node.js"] = ["JavaScript", "TypeScript"], ["Go"] = ["Go"], ["Golang"] = ["Go"], ["Rust"] = ["Rust"], ["Ruby"] = ["Ruby"], ["PHP"] = ["PHP"], ["Swift"] = ["Swift"], ["Kotlin"] = ["Kotlin"], ["C++"] = ["C++", "C"], ["C"] = ["C"], ["Scala"] = ["Scala"], ["R"] = ["R"], ["SQL"] = ["PLSQL", "TSQL"], ["Shell"] = ["Shell", "Bash", "PowerShell"], ["DevOps"] = ["Shell", "Dockerfile", "HCL"], ["Docker"] = ["Dockerfile"], ["Terraform"] = ["HCL"], ["Mobile"] = ["Swift", "Kotlin", "Dart", "Java"], ["iOS"] = ["Swift", "Objective-C"], ["Android"] = ["Kotlin", "Java"], ["Flutter"] = ["Dart"], ["Machine Learning"] = ["Python", "Jupyter Notebook", "R"], ["Data Science"] = ["Python", "Jupyter Notebook", "R"], }; public GitHubVerifierService( GitHubApiClient gitHubClient, ILogger logger) { _gitHubClient = gitHubClient; _logger = logger; } public async Task VerifyProfileAsync(string username) { try { _logger.LogInformation("Verifying GitHub profile: {Username}", username); var user = await _gitHubClient.GetUserAsync(username); if (user == null) { return new GitHubVerificationResult { ClaimedUsername = username, IsVerified = false, VerificationNotes = "GitHub profile not found" }; } // Get repositories for language analysis var repos = await _gitHubClient.GetUserReposAsync(username); // Analyze languages var languageStats = new Dictionary(); foreach (var repo in repos.Where(r => !r.Fork && !string.IsNullOrEmpty(r.Language))) { if (!languageStats.ContainsKey(repo.Language!)) languageStats[repo.Language!] = 0; languageStats[repo.Language!]++; } // Calculate flags var flags = new List(); // Check account age var accountAge = DateTime.UtcNow - user.CreatedAt; if (accountAge.TotalDays < 90) { flags.Add(new GitHubVerificationFlag { Type = "NewAccount", Severity = "Info", Message = "Account created less than 90 days ago", ScoreImpact = -5 }); } // Check for empty profile if (user.PublicRepos == 0) { flags.Add(new GitHubVerificationFlag { Type = "NoRepos", Severity = "Warning", Message = "No public repositories", ScoreImpact = -10 }); } return new GitHubVerificationResult { ClaimedUsername = username, IsVerified = true, ProfileName = user.Name, ProfileUrl = user.HtmlUrl, Bio = user.Bio, Company = user.Company, Location = user.Location, AccountCreated = user.CreatedAt != default ? DateOnly.FromDateTime(user.CreatedAt) : null, PublicRepos = user.PublicRepos, Followers = user.Followers, Following = user.Following, LanguageStats = languageStats, VerificationNotes = BuildVerificationSummary(user, repos), Flags = flags }; } catch (Exception ex) { _logger.LogError(ex, "Error verifying GitHub profile: {Username}", username); return new GitHubVerificationResult { ClaimedUsername = username, IsVerified = false, VerificationNotes = $"Error during verification: {ex.Message}" }; } } public async Task VerifySkillsAsync( string username, List claimedSkills) { var result = await VerifyProfileAsync(username); if (!result.IsVerified) return result; var skillVerifications = new List(); foreach (var skill in claimedSkills) { var verified = false; var repoCount = 0; // Check if the skill matches a known language directly if (result.LanguageStats.TryGetValue(skill, out var count)) { verified = true; repoCount = count; } else if (SkillToLanguageMap.TryGetValue(skill, out var mappedLanguages)) { // Check if any mapped language exists in the user's repos foreach (var lang in mappedLanguages) { if (result.LanguageStats.TryGetValue(lang, out var langCount)) { verified = true; repoCount += langCount; } } } skillVerifications.Add(new SkillVerification { ClaimedSkill = skill, IsVerified = verified, RepoCount = repoCount, Notes = verified ? $"Found in {repoCount} repositories" : "No repositories found using this skill" }); } var verifiedCount = skillVerifications.Count(sv => sv.IsVerified); var totalCount = skillVerifications.Count; var percentage = totalCount > 0 ? (verifiedCount * 100) / totalCount : 0; return result with { SkillVerifications = skillVerifications, VerificationNotes = $"Skills verified: {verifiedCount}/{totalCount} ({percentage}%)" }; } public async Task> SearchProfilesAsync(string name) { try { var searchResponse = await _gitHubClient.SearchUsersAsync(name); if (searchResponse?.Items == null) { return []; } var results = new List(); foreach (var item in searchResponse.Items.Take(10)) { if (string.IsNullOrEmpty(item.Login)) continue; // Get full profile details var user = await _gitHubClient.GetUserAsync(item.Login); results.Add(new GitHubProfileSearchResult { Username = item.Login, Name = user?.Name, AvatarUrl = item.AvatarUrl, Bio = user?.Bio, PublicRepos = user?.PublicRepos ?? 0, Followers = user?.Followers ?? 0 }); } return results; } catch (Exception ex) { _logger.LogError(ex, "Error searching GitHub profiles: {Name}", name); return []; } } private static string BuildVerificationSummary(GitHubUser user, List repos) { var parts = new List { $"Account created: {user.CreatedAt:yyyy-MM-dd}", $"Public repos: {user.PublicRepos}", $"Followers: {user.Followers}" }; var totalStars = repos.Sum(r => r.StargazersCount); if (totalStars > 0) { parts.Add($"Total stars: {totalStars}"); } var topLanguages = repos .Where(r => !r.Fork && !string.IsNullOrEmpty(r.Language)) .GroupBy(r => r.Language) .OrderByDescending(g => g.Count()) .Take(3) .Select(g => g.Key); if (topLanguages.Any()) { parts.Add($"Top languages: {string.Join(", ", topLanguages)}"); } return string.Join(" | ", parts); } }