namespace AAIntegration.SimmonsBank.API.Services;

using AutoMapper;
using BCrypt.Net;
using AAIntegration.SimmonsBank.API.Entities;
using AAIntegration.SimmonsBank.API.Config;
using AAIntegration.SimmonsBank.API.Models.Accounts;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System;
using Internal;
using Microsoft.EntityFrameworkCore;

public interface IAccountService
{
    IEnumerable<Account> GetAll(int ownerId);
    Account GetById(int accountId, int ownerId);
    void Create(AccountCreateRequest model, int ownerId);
    void Update(int accountId, AccountUpdateRequest model, int ownerId);
    void Delete(int accountId, int ownerId);
}

public class AccountService : IAccountService
{
    private DataContext _context;
    private readonly IMapper _mapper;
    private IUserService _userService;

    public AccountService(
        DataContext context,
        IMapper mapper,
        IUserService userService)
    {
        _context = context;
        _mapper = mapper;
        _userService = userService;
    }

    public IEnumerable<Account> GetAll(int ownerId)
    {
        return _context.Accounts
            .Include(x => x.Owner)
            .Where(x => x.Owner.Id == ownerId);
    }

    public Account GetById(int accountId, int ownerId)
    {
        return getAccount(accountId, ownerId);
    }

    public void Create(AccountCreateRequest model, int ownerId)
    {
        // Check that account with same name or same external number doesn't exist
        IEnumerable<Account> accountsWithSameName = _context.Accounts
            .Include(x => x.Owner)
            .Where(x => x.Name.ToUpper() == model.Name.ToUpper() && x.Owner.Id == ownerId);

        if (accountsWithSameName.Count() > 0)
            throw new AppException("Account with name '" + model.Name + "' already exists");

        if (!string.IsNullOrWhiteSpace(model.ExternalAccountNumber))
        {
            IEnumerable<Account> matches = _context.Accounts
                .Include(x => x.Owner)
                .Where(x => x.ExternalAccountNumber == model.ExternalAccountNumber && x.Owner.Id == ownerId);

            if (matches.Count() > 0)
                throw new AppException("Account with external account number '" + model.ExternalAccountNumber + "' already exists under account named '" + matches.First().Name + "'");
        }

        Account account = new Account {
            Name = model.Name,
            Balance = Convert.ToDecimal(model.InitialBalance),
            ExternalAccountNumber = model.ExternalAccountNumber,
            Owner = getOwner(ownerId)
        };

        _context.Accounts.Add(account);
        _context.SaveChanges();
    }

    public void Update(int accountId, AccountUpdateRequest model, int ownerId)
    {
        Account account = getAccount(accountId, ownerId);

        // validate
        if (model.Name != account.Name && _context.Accounts
            .Include(x => x.Owner)
            .Any(x => x.Name == model.Name && x.Owner.Id == ownerId))
            throw new AppException("Account with the name '" + model.Name + "' already exists");

        // Name
        if (!string.IsNullOrWhiteSpace(model.Name))
            account.Name = model.Name;

        // External Account Number
        if (!string.IsNullOrWhiteSpace(model.ExternalAccountNumber))
            account.ExternalAccountNumber = model.ExternalAccountNumber;

        _context.Accounts.Update(account);
        _context.SaveChanges();
    }

    public void Delete(int accountId, int ownerId)
    {
        var account = getAccount(accountId, ownerId);
        _context.Accounts.Remove(account);
        _context.SaveChanges();
    }

    // helper methods

    private Account getAccount(int id, int ownerId)
    {
        var account = _context.Accounts
            .Include(x => x.Owner)
            .FirstOrDefault(x => x.Id == id && x.Owner.Id == ownerId);
        if (account == null) throw new KeyNotFoundException("Account not found");
        return account;
    }

    private User getOwner(int ownerId)
    {
        User? owner = _context.Users.Find(ownerId);

        if (owner == null)
            throw new AppException($"Owner with ID of '{ownerId}' could not be found");

        return owner;
    }
}