using CartWise.Application.Interfaces; using CartWise.Domain.Enums; using CartWise.Infrastructure.Identity; using CartWise.Web.ViewModels.Price; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; namespace CartWise.Web.Controllers; [Authorize] [Route("prices")] public class PriceController : Controller { private readonly IPriceService _priceService; private readonly IProductService _productService; private readonly IHouseholdService _householdService; private readonly UserManager _userManager; public PriceController( IPriceService priceService, IProductService productService, IHouseholdService householdService, UserManager userManager) { _priceService = priceService; _productService = productService; _householdService = householdService; _userManager = userManager; } [HttpGet("")] public async Task Index(string? sort) { var household = await CurrentHouseholdOrNullAsync(); if (household is null) { return RedirectToAction("Create", "Household"); } var products = await _priceService.GetHouseholdPricedProductsAsync(household.HouseholdId); IEnumerable ordered = sort switch { "price-asc" => products.OrderBy(p => p.LatestPrice), "price-desc" => products.OrderByDescending(p => p.LatestPrice), "recent" => products.OrderByDescending(p => p.LatestObservedUtc), _ => products.OrderBy(p => p.ProductName) }; var viewModel = new PriceIndexViewModel { Sort = sort ?? "name", Products = ordered.Select(p => new PricedProductRowViewModel { ProductId = p.ProductId, ProductName = p.ProductName, LatestPrice = p.LatestPrice, LatestObservedUtc = p.LatestObservedUtc, AveragePrice = p.AveragePrice, LowestPrice = p.LowestPrice, ObservationCount = p.ObservationCount }).ToList() }; return View(viewModel); } [HttpGet("product/{productId:guid}")] public async Task Product(Guid productId) { var household = await CurrentHouseholdOrNullAsync(); if (household is null) { return RedirectToAction("Create", "Household"); } var details = await _productService.GetDetailsAsync(productId); if (details is null) { return NotFound(); } var insight = await _priceService.GetHouseholdPriceInsightAsync(household.HouseholdId, productId); var history = await _priceService.GetHouseholdPriceHistoryAsync(household.HouseholdId, productId); var viewModel = new PriceProductViewModel { ProductId = productId, ProductName = details.Name, LatestPrice = insight?.LatestPrice, LatestObservedUtc = insight?.LatestObservedUtc, AveragePrice = insight?.AveragePrice, MedianPrice = insight?.MedianPrice, LowestPrice = insight?.LowestPrice, UnitPrice = insight?.UnitPrice, ObservationCount = insight?.ObservationCount ?? 0, History = history.Select(h => new PriceObservationRowViewModel { Price = h.Price, UnitPrice = h.UnitPrice, ObservedUtc = h.ObservedUtc, SourceType = h.SourceType }).ToList() }; return View(viewModel); } [HttpPost("product/{productId:guid}/observe")] [ValidateAntiForgeryToken] public async Task Observe(Guid productId, decimal price, DateTime? observedOn) { var household = await CurrentHouseholdOrNullAsync(); if (household is null) { return RedirectToAction("Create", "Household"); } if (price <= 0) { ModelState.AddModelError(string.Empty, "Price must be greater than zero."); return RedirectToAction(nameof(Product), new { productId }); } var observedUtc = observedOn ?? DateTime.UtcNow; await _priceService.RecordObservationAsync( productId, price, observedUtc, PriceObservationSourceType.UserEntered, confidenceScore: 1m, householdId: household.HouseholdId); return RedirectToAction(nameof(Product), new { productId }); } private async Task CurrentHouseholdOrNullAsync() { var userId = _userManager.GetUserId(User)!; return await _householdService.GetCurrentHouseholdAsync(userId); } }