package com.expensetracker.api.expenses.service;

import com.expensetracker.api.expenses.dto.CreateExpenseRequest;
import com.expensetracker.api.expenses.dto.ExpenseResponse;
import com.expensetracker.api.expenses.model.Category;
import com.expensetracker.api.expenses.model.Expense;
import com.expensetracker.api.expenses.repository.CategoryRepository;
import com.expensetracker.api.expenses.repository.ExpenseRepository;
import com.expensetracker.api.security.support.CurrentUser;
import java.time.LocalDate;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.stereotype.Service;
import org.springframework.web.server.ResponseStatusException;

import static org.springframework.http.HttpStatus.NOT_FOUND;

@Service
public class ExpenseService {

  private final ExpenseRepository repo;
  private final CategoryRepository categories;

  public ExpenseService(ExpenseRepository repo, CategoryRepository categories) {
    this.repo = repo;
    this.categories = categories;
  }

  public ExpenseResponse create(CreateExpenseRequest req) {
    long userId = CurrentUser.id();

    Expense e = Expense.builder()
        .userId(userId)
        .title(req.title())
        .amountCents(req.amountCents())
        .category(resolveCategory(userId, req.category()))
        .currency(req.currency() == null || req.currency().isBlank() ? "CHF" : req.currency().toUpperCase())
        .expenseDate(req.expenseDate() == null ? LocalDate.now() : req.expenseDate())
        .note(req.note())
        .build();

    Expense saved = repo.save(e);
    return toDto(saved);
  }

  public Page<ExpenseResponse> list(LocalDate from, LocalDate to, Pageable pageable) {
    long userId = CurrentUser.id();
    if (from != null || to != null) {
      LocalDate effectiveFrom = from != null ? from : LocalDate.of(1970, 1, 1);
      LocalDate effectiveTo = to != null ? to : LocalDate.of(9999, 12, 31);
      return repo.findByUserIdAndExpenseDateBetweenOrderByCreatedAtDesc(userId, effectiveFrom, effectiveTo, pageable)
          .map(this::toDto);
    }

    return repo.findByUserIdOrderByCreatedAtDesc(userId, pageable).map(this::toDto);
  }

  public ExpenseResponse update(long id, CreateExpenseRequest req) {
    long userId = CurrentUser.id();
    Expense e = repo.findById(id)
        .orElseThrow(() -> new ResponseStatusException(NOT_FOUND, "Expense not found"));

    if (!e.getUserId().equals(userId)) {
      throw new AccessDeniedException("Not your expense");
    }

    e.setTitle(req.title());
    e.setAmountCents(req.amountCents());
    e.setCategory(resolveCategory(userId, req.category()));
    e.setCurrency(req.currency() == null || req.currency().isBlank() ? "CHF" : req.currency().toUpperCase());
    e.setExpenseDate(req.expenseDate() == null ? e.getExpenseDate() : req.expenseDate());
    e.setNote(req.note());

    Expense updated = repo.save(e);
    return toDto(updated);
  }

  public void delete(long id) {
    long userId = CurrentUser.id();
    Expense e = repo.findById(id)
        .orElseThrow(() -> new ResponseStatusException(NOT_FOUND, "Expense not found"));

    if (!e.getUserId().equals(userId)) {
      throw new AccessDeniedException("Not your expense");
    }

    repo.delete(e);
  }

  private ExpenseResponse toDto(Expense e) {
    return new ExpenseResponse(
        e.getId(),
        e.getTitle(),
        e.getAmountCents(),
        e.getCategory() == null ? null : e.getCategory().getName(),
        e.getCurrency(),
        e.getExpenseDate(),
        e.getNote(),
        e.getCreatedAt(),
        e.getUpdatedAt()
    );
  }

  private Category resolveCategory(long userId, String categoryName) {
    String normalized = (categoryName == null || categoryName.isBlank()) ? "General" : categoryName.trim();

    return categories.findByUserIdAndNameIgnoreCase(userId, normalized)
        .orElseGet(() -> categories.save(Category.builder()
            .userId(userId)
            .name(normalized)
            .build()));
  }
}
