using Api.SeaHavenIndustries.DTOs; using Data.SeaHavenIndustries; using FluentValidation; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using SeaHaven.Services.Interfaces; using SeaHaven.Services.DTOs; using System.Security.Claims; namespace Api.SeaHavenIndustries.Controllers { [Authorize] [ApiController] [Route("api/[controller]")] [Route("api/vendors")] public class VendorController : Controller { private readonly IVendorService _vendorService; private readonly ApplicationDbContext _db; // Keep for complex queries like nearby vendors private readonly Helper.ZipCodeDistance _zipDistance; private readonly Helper.VendorPortalTokenService _vendorTokens; private readonly IConfiguration _config; public VendorController(IVendorService vendorService, ApplicationDbContext db, Helper.ZipCodeDistance zipDistance, Helper.VendorPortalTokenService vendorTokens, IConfiguration config) { _vendorService = vendorService; _db = db; _zipDistance = zipDistance; _vendorTokens = vendorTokens; _config = config; } [HttpGet("GetVendorList")] public async Task GetVendorList(string? search = "", int page = 1, int pageSize = 10) { var pagedResult = await _vendorService.GetVendorsPagedAsync(page, pageSize, search); var data = pagedResult.Items.Select(v => new { v.Id, CompanyName = v.Name, v.Email, v.Phone }); return Ok(new Pagination_DTO { Data = data, PageNumber = page, PageSize = pageSize, TotalCount = pagedResult.TotalCount, TotalPages = (int)Math.Ceiling(pagedResult.TotalCount / (double)pageSize) }); } [HttpGet("{id}")] [HttpGet("GetById")] public async Task GetVendorById([FromQuery] int? id) { if (id == null) return BadRequest(new Response { Status = "Error", Message = "Id is required" }); var vendor = await _vendorService.GetVendorByIdAsync(id.Value); if (vendor == null) return NotFound(new Response { Status = "Error", Message = "Vendor not found" }); return Ok(vendor); } [HttpPost] [HttpPost("Create")] public async Task Create([FromBody] Vendor_DTO model) { try { var createDto = new CreateVendorDTO { Name = model.CompanyName ?? throw new ArgumentException("CompanyName is required") }; var userId = User.FindFirstValue(ClaimTypes.NameIdentifier); if (userId == null) return Unauthorized(new Response { Status = "Error", Message = "User not authenticated" }); await _vendorService.CreateVendorAsync(createDto, userId); return Ok(new DataResponse { Message = "Vendor Created", Status = "200" }); } catch (ValidationException vex) { var errors = string.Join(", ", vex.Errors.Select(e => e.ErrorMessage)); return BadRequest(new Response { Status = "Validation Error", Message = errors }); } catch (Exception ex) { return StatusCode(500, new Response { Status = "Error", Message = ex.Message }); } } [HttpPut("{id}")] [HttpPost("Update")] public async Task Update([FromRoute] int? id, [FromBody] EditVendor_DTO model) { try { // For named route, id comes from model int vendorId = id ?? model.Id; if (vendorId == 0) return BadRequest(new Response { Status = "Error", Message = "Vendor Id is required" }); var updateDto = new UpdateVendorDTO { Name = model.CompanyName }; var userId = User.FindFirstValue(ClaimTypes.NameIdentifier); if (userId == null) return Unauthorized(new Response { Status = "Error", Message = "User not authenticated" }); await _vendorService.UpdateVendorAsync(vendorId, updateDto, userId); return Ok(new DataResponse { Message = "Vendor Updated", Status = "200" }); } catch (ValidationException vex) { var errors = string.Join(", ", vex.Errors.Select(e => e.ErrorMessage)); return BadRequest(new Response { Status = "Validation Error", Message = errors }); } catch (KeyNotFoundException) { return NotFound(new Response { Status = "Error", Message = "Vendor not found" }); } catch (Exception ex) { return StatusCode(500, new Response { Status = "Error", Message = ex.Message }); } } [HttpDelete("{id}")] [HttpPost("Delete")] public async Task Delete([FromRoute] int? id, [FromQuery] int? queryId = null) { try { // Support both route parameter and query string int vendorId = id ?? queryId ?? 0; if (vendorId == 0) return BadRequest(new Response { Status = "Error", Message = "Vendor Id is required" }); var userId = User.FindFirstValue(ClaimTypes.NameIdentifier); if (userId == null) return Unauthorized(new Response { Status = "Error", Message = "User not authenticated" }); await _vendorService.DeleteVendorAsync(vendorId, userId); return Ok(new DataResponse { Message = "Vendor Deleted", Status = "200" }); } catch (KeyNotFoundException) { return NotFound(new Response { Status = "Error", Message = "Vendor not found" }); } catch (Exception ex) { return StatusCode(500, new Response { Status = "Error", Message = ex.Message }); } } [HttpGet("Dropdown")] public async Task GetDropdown([FromQuery] string? trade = null, [FromQuery] string? siteZip = null) { var query = _db.Vendors.Where(v => v.IsActive); var vendors = await query.OrderBy(v => v.CompanyName) .Select(v => new { v.Id, v.CompanyName, v.TradeSpecialties, v.Address, v.City, v.State, v.Zip }) .ToListAsync(); var result = vendors.Select(v => { var distance = _zipDistance.GetDistanceMiles(siteZip, v.Zip); var addr = new[] { v.Address, v.City, v.State, v.Zip } .Where(s => !string.IsNullOrWhiteSpace(s)); return new { v.Id, v.CompanyName, v.TradeSpecialties, address = string.Join(", ", addr), distanceMiles = distance.HasValue ? Math.Round(distance.Value, 1) : (double?)null }; }).ToList(); if (!string.IsNullOrWhiteSpace(trade)) { var tradeMatched = result.Where(v => (v.TradeSpecialties ?? "").Contains(trade)).ToList(); var rest = result.Where(v => !(v.TradeSpecialties ?? "").Contains(trade)).ToList(); result = tradeMatched.Concat(rest).ToList(); } if (!string.IsNullOrWhiteSpace(siteZip)) { result = result.OrderBy(v => v.distanceMiles ?? 99999).ToList(); } return Ok(result); } [HttpGet("{id:int}/portal-token")] public async Task GetPortalToken(int id) { var vendor = await _db.Vendors.FindAsync(id); if (vendor == null) return NotFound(); var token = await _vendorTokens.GetOrCreateActiveTokenAsync(id); return Ok(BuildPortalTokenResponse(token)); } [HttpPost("{id:int}/portal-token/rotate")] public async Task RotatePortalToken(int id) { var vendor = await _db.Vendors.FindAsync(id); if (vendor == null) return NotFound(); var token = await _vendorTokens.RotateAsync(id); return Ok(BuildPortalTokenResponse(token)); } [HttpPost("{id:int}/portal-token/revoke")] public async Task RevokePortalToken(int id) { var vendor = await _db.Vendors.FindAsync(id); if (vendor == null) return NotFound(); await _vendorTokens.RevokeAllAsync(id); return Ok(new { revoked = true }); } private object BuildPortalTokenResponse(VendorAccessToken token) { var frontendBase = _config.GetValue("FrontendBaseUrl")?.TrimEnd('/') ?? ""; return new { token.Token, token.IssuedAt, token.ExpiresAt, token.LastUsedAt, PortalUrl = $"{frontendBase}/v/{token.Token}/dashboard" }; } } }