using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using OmsorgCore.Api.Contracts;
using OmsorgCore.Api.Security;
using OmsorgCore.Application.Abstractions;
using OmsorgCore.Application.Services;
using OmsorgCore.Domain.Entities;
using OmsorgCore.Domain.Enums;
using OmsorgCore.Engine.Events;

namespace OmsorgCore.Api.Controllers;

[ApiController]
[Authorize]
[Route("api/facilities")]
public class FacilitiesController : ControllerBase
{
    private record CrmStatusValidationResult(string? ErrorMessage, DateTime? FollowUpDueDate);

    private const string CrmStatusListKey = "CrmStatus";
    private const string FacilityTypeListKey = "FacilityType";
    private const string FollowUpPeriodsListKey = "FollowUpPeriods";
    private const string BillingIntervalListKey = "BillingInterval";
    private static readonly string[] TravelCostModes = { "Pauschale", "ProKilometer" };

    private readonly IFacilityService _facilityService;
    private readonly IValueListRepository _valueListRepository;
    private readonly IDomainEventDispatcher _dispatcher;

    public FacilitiesController(IFacilityService facilityService, IValueListRepository valueListRepository, IDomainEventDispatcher dispatcher)
    {
        _facilityService = facilityService;
        _valueListRepository = valueListRepository;
        _dispatcher = dispatcher;
    }

    [HttpGet]
    [RequirePermission(ModuleType.Facilities, PermissionAction.View)]
    public async Task<ActionResult<PagedResponse<FacilityResponse>>> GetAll(
        [FromQuery] string? search,
        [FromQuery] string? crmStatus,
        [FromQuery] bool followUpDueOnly = false,
        [FromQuery] int page = 1,
        [FromQuery] int pageSize = 20,
        CancellationToken cancellationToken = default)
    {
        page = Math.Max(page, 1);
        pageSize = Math.Clamp(pageSize, 1, 100);

        var (items, totalCount) = await _facilityService.GetPagedAsync(search, crmStatus, followUpDueOnly, page, pageSize, cancellationToken);
        return Ok(new PagedResponse<FacilityResponse>(items.Select(ToResponse).ToList(), totalCount, page, pageSize));
    }

    [HttpGet("{id:guid}")]
    [RequirePermission(ModuleType.Facilities, PermissionAction.View)]
    public async Task<ActionResult<FacilityResponse>> GetById(Guid id, CancellationToken cancellationToken)
    {
        var facility = await _facilityService.GetByIdAsync(id, cancellationToken);
        return facility is null ? NotFound() : Ok(ToResponse(facility));
    }

    [HttpPost]
    [RequirePermission(ModuleType.Facilities, PermissionAction.Create)]
    public async Task<ActionResult<FacilityResponse>> Create(CreateFacilityRequest request, CancellationToken cancellationToken)
    {
        if (string.IsNullOrWhiteSpace(request.Name) || request.Name.Length > 300)
        {
            return BadRequest("Name ist erforderlich und darf maximal 300 Zeichen lang sein.");
        }

        if (string.IsNullOrWhiteSpace(request.CrmStatus) || request.CrmStatus.Length > 50)
        {
            return BadRequest("CrmStatus ist erforderlich und darf maximal 50 Zeichen lang sein.");
        }

        if (request.FacilityType is { Length: > 100 })
        {
            return BadRequest("FacilityType darf maximal 100 Zeichen lang sein.");
        }

        if (request.FacilityType is not null)
        {
            var allowedTypes = await _valueListRepository.GetActiveValuesAsync(FacilityTypeListKey, cancellationToken);
            if (!allowedTypes.Contains(request.FacilityType))
            {
                return BadRequest($"FacilityType muss einer der folgenden Werte sein: {string.Join(", ", allowedTypes)}.");
            }
        }

        if (request.Website is { Length: > 300 })
        {
            return BadRequest("Website darf maximal 300 Zeichen lang sein.");
        }

        var addressError = ValidateAddressFields(request.Street, request.PostalCode, request.City, request.Country, prefix: "");
        if (addressError is not null)
        {
            return BadRequest(addressError);
        }

        var billingAddressError = ValidateAddressFields(request.BillingStreet, request.BillingPostalCode, request.BillingCity, request.BillingCountry, prefix: "Billing");
        if (billingAddressError is not null)
        {
            return BadRequest(billingAddressError);
        }

        var crmStatusValidationError = await ValidateCrmStatusAndFollowUpAsync(
            request.CrmStatus,
            request.FollowUpDays,
            existingCrmStatus: null,
            cancellationToken);
        if (crmStatusValidationError.ErrorMessage is not null)
        {
            return BadRequest(crmStatusValidationError.ErrorMessage);
        }

        if (request.IndividualAgreements is { Length: > 2000 })
        {
            return BadRequest("IndividualAgreements darf maximal 2000 Zeichen lang sein.");
        }

        if (request.BillingInterval is not null)
        {
            var allowedBillingIntervals = await _valueListRepository.GetActiveValuesAsync(BillingIntervalListKey, cancellationToken);
            if (!allowedBillingIntervals.Contains(request.BillingInterval))
            {
                return BadRequest($"BillingInterval muss einer der folgenden Werte sein: {string.Join(", ", allowedBillingIntervals)}.");
            }
        }

        if (request.BillingRate is < 0
            || request.TravelCostRate is < 0
            || request.TravelCostPerKm is < 0
            || request.MealAllowanceRate is < 0
            || request.MinimumHours is < 0
            || request.NightSurchargePercent is < 0
            || request.SaturdaySurchargePercent is < 0
            || request.SundaySurchargePercent is < 0
            || request.HolidaySurchargePercent is < 0
            || request.PaymentTermDays is < 0)
        {
            return BadRequest("Konditionswerte dürfen nicht negativ sein.");
        }

        if (!TravelCostModes.Contains(request.TravelCostMode))
        {
            return BadRequest($"TravelCostMode muss einer der folgenden Werte sein: {string.Join(", ", TravelCostModes)}.");
        }

        var facility = new Facility
        {
            Name = request.Name,
            CrmStatus = request.CrmStatus,
            FollowUpDueDate = crmStatusValidationError.FollowUpDueDate,
            BillingRate = request.BillingRate,
            NightSurchargePercent = request.NightSurchargePercent,
            SaturdaySurchargePercent = request.SaturdaySurchargePercent,
            SundaySurchargePercent = request.SundaySurchargePercent,
            HolidaySurchargePercent = request.HolidaySurchargePercent,
            TravelCostRate = request.TravelCostRate,
            TravelCostMode = request.TravelCostMode,
            TravelCostPerKm = request.TravelCostPerKm,
            MealAllowanceRate = request.MealAllowanceRate,
            MinimumHours = request.MinimumHours,
            BillingInterval = request.BillingInterval,
            PaymentTermDays = request.PaymentTermDays,
            IndividualAgreements = request.IndividualAgreements,
            FacilityType = request.FacilityType,
            Website = request.Website,
            Street = request.Street,
            PostalCode = request.PostalCode,
            City = request.City,
            Country = request.Country,
            BillingStreet = request.BillingStreet,
            BillingPostalCode = request.BillingPostalCode,
            BillingCity = request.BillingCity,
            BillingCountry = request.BillingCountry
        };

        var created = await _facilityService.CreateAsync(facility, cancellationToken);
        await _dispatcher.DispatchAsync(new FacilityCreatedEvent(created.Id), cancellationToken);

        return CreatedAtAction(nameof(GetById), new { id = created.Id }, ToResponse(created));
    }

    [HttpPut("{id:guid}")]
    [RequirePermission(ModuleType.Facilities, PermissionAction.Edit)]
    public async Task<ActionResult<FacilityResponse>> Update(Guid id, UpdateFacilityRequest request, CancellationToken cancellationToken)
    {
        var existing = await _facilityService.GetByIdAsync(id, cancellationToken);
        if (existing is null)
        {
            return NotFound();
        }

        if (string.IsNullOrWhiteSpace(request.Name) || request.Name.Length > 300)
        {
            return BadRequest("Name ist erforderlich und darf maximal 300 Zeichen lang sein.");
        }

        if (string.IsNullOrWhiteSpace(request.CrmStatus) || request.CrmStatus.Length > 50)
        {
            return BadRequest("CrmStatus ist erforderlich und darf maximal 50 Zeichen lang sein.");
        }

        var crmStatusValidationError = await ValidateCrmStatusAndFollowUpAsync(
            request.CrmStatus,
            request.FollowUpDays,
            existingCrmStatus: existing.CrmStatus,
            cancellationToken);
        if (crmStatusValidationError.ErrorMessage is not null)
        {
            return BadRequest(crmStatusValidationError.ErrorMessage);
        }

        var followUpDueDate = crmStatusValidationError.FollowUpDueDate;
        if (followUpDueDate is null && existing.CrmStatus == request.CrmStatus)
        {
            followUpDueDate = existing.FollowUpDueDate;
        }

        if (request.FacilityType is { Length: > 100 })
        {
            return BadRequest("FacilityType darf maximal 100 Zeichen lang sein.");
        }

        if (request.FacilityType is not null)
        {
            var allowedTypes = await _valueListRepository.GetActiveValuesAsync(FacilityTypeListKey, cancellationToken);
            if (!allowedTypes.Contains(request.FacilityType))
            {
                return BadRequest($"FacilityType muss einer der folgenden Werte sein: {string.Join(", ", allowedTypes)}.");
            }
        }

        if (request.Website is { Length: > 300 })
        {
            return BadRequest("Website darf maximal 300 Zeichen lang sein.");
        }

        var addressError = ValidateAddressFields(request.Street, request.PostalCode, request.City, request.Country, prefix: "");
        if (addressError is not null)
        {
            return BadRequest(addressError);
        }

        var billingAddressError = ValidateAddressFields(request.BillingStreet, request.BillingPostalCode, request.BillingCity, request.BillingCountry, prefix: "Billing");
        if (billingAddressError is not null)
        {
            return BadRequest(billingAddressError);
        }

        if (request.IndividualAgreements is { Length: > 2000 })
        {
            return BadRequest("IndividualAgreements darf maximal 2000 Zeichen lang sein.");
        }

        if (request.BillingInterval is not null)
        {
            var allowedBillingIntervals = await _valueListRepository.GetActiveValuesAsync(BillingIntervalListKey, cancellationToken);
            if (!allowedBillingIntervals.Contains(request.BillingInterval))
            {
                return BadRequest($"BillingInterval muss einer der folgenden Werte sein: {string.Join(", ", allowedBillingIntervals)}.");
            }
        }

        if (request.BillingRate is < 0
            || request.TravelCostRate is < 0
            || request.TravelCostPerKm is < 0
            || request.MealAllowanceRate is < 0
            || request.MinimumHours is < 0
            || request.NightSurchargePercent is < 0
            || request.SaturdaySurchargePercent is < 0
            || request.SundaySurchargePercent is < 0
            || request.HolidaySurchargePercent is < 0
            || request.PaymentTermDays is < 0)
        {
            return BadRequest("Konditionswerte dürfen nicht negativ sein.");
        }

        if (!TravelCostModes.Contains(request.TravelCostMode))
        {
            return BadRequest($"TravelCostMode muss einer der folgenden Werte sein: {string.Join(", ", TravelCostModes)}.");
        }

        var updates = new Facility
        {
            Name = request.Name,
            CrmStatus = request.CrmStatus,
            FollowUpDueDate = followUpDueDate,
            BillingRate = request.BillingRate,
            NightSurchargePercent = request.NightSurchargePercent,
            SaturdaySurchargePercent = request.SaturdaySurchargePercent,
            SundaySurchargePercent = request.SundaySurchargePercent,
            HolidaySurchargePercent = request.HolidaySurchargePercent,
            TravelCostRate = request.TravelCostRate,
            TravelCostMode = request.TravelCostMode,
            TravelCostPerKm = request.TravelCostPerKm,
            MealAllowanceRate = request.MealAllowanceRate,
            MinimumHours = request.MinimumHours,
            BillingInterval = request.BillingInterval,
            PaymentTermDays = request.PaymentTermDays,
            IndividualAgreements = request.IndividualAgreements,
            FacilityType = request.FacilityType,
            Website = request.Website,
            Street = request.Street,
            PostalCode = request.PostalCode,
            City = request.City,
            Country = request.Country,
            BillingStreet = request.BillingStreet,
            BillingPostalCode = request.BillingPostalCode,
            BillingCity = request.BillingCity,
            BillingCountry = request.BillingCountry
        };

        var updated = await _facilityService.UpdateAsync(id, updates, cancellationToken);
        return updated is null ? NotFound() : Ok(ToResponse(updated));
    }

    [HttpDelete("{id:guid}")]
    [RequirePermission(ModuleType.Facilities, PermissionAction.Delete)]
    public async Task<IActionResult> Delete(Guid id, CancellationToken cancellationToken)
    {
        var deleted = await _facilityService.DeleteAsync(id, cancellationToken);
        return deleted ? NoContent() : NotFound();
    }

    private async Task<CrmStatusValidationResult> ValidateCrmStatusAndFollowUpAsync(
        string crmStatus,
        int? followUpDays,
        string? existingCrmStatus,
        CancellationToken cancellationToken)
    {
        var crmStatusItems = await _valueListRepository.GetItemsAsync(CrmStatusListKey, cancellationToken);
        var selectedCrmStatusItem = crmStatusItems.FirstOrDefault(i => i.Value == crmStatus);
        if (selectedCrmStatusItem is null)
        {
            return new($"CrmStatus muss einer der folgenden Werte sein: {string.Join(", ", crmStatusItems.Select(i => i.Value))}.", null);
        }

        if (existingCrmStatus is not null)
        {
            var currentCrmStatusItem = crmStatusItems.FirstOrDefault(i => i.Value == existingCrmStatus);
            if (currentCrmStatusItem is not null
                && !await _valueListRepository.CanTransitionAsync(currentCrmStatusItem.Id, selectedCrmStatusItem.Id, cancellationToken))
            {
                return new("Der Statuswechsel ist nicht zulässig.", null);
            }
        }

        DateTime? followUpDueDate = null;
        if (selectedCrmStatusItem.TriggersFollowUp)
        {
            if (existingCrmStatus is not null && existingCrmStatus == crmStatus)
            {
                followUpDueDate = null;
            }
            else
            {
                var allowedFollowUpPeriods = await _valueListRepository.GetActiveValuesAsync(FollowUpPeriodsListKey, cancellationToken);
                if (followUpDays is null || !allowedFollowUpPeriods.Contains(followUpDays.Value.ToString()))
                {
                    return new($"FollowUpDays ist bei CrmStatus \"{crmStatus}\" erforderlich und muss einer der folgenden Werte sein: {string.Join(", ", allowedFollowUpPeriods)}.", null);
                }

                followUpDueDate = DateTime.UtcNow.AddDays(followUpDays.Value);
            }
        }

        return new(null, followUpDueDate);
    }

    private static string? ValidateAddressFields(string? street, string? postalCode, string? city, string? country, string prefix)
    {
        if (street is { Length: > 200 })
        {
            return $"{prefix}Street darf maximal 200 Zeichen lang sein.";
        }

        if (postalCode is { Length: > 10 })
        {
            return $"{prefix}PostalCode darf maximal 10 Zeichen lang sein.";
        }

        if (city is { Length: > 100 })
        {
            return $"{prefix}City darf maximal 100 Zeichen lang sein.";
        }

        if (country is { Length: > 100 })
        {
            return $"{prefix}Country darf maximal 100 Zeichen lang sein.";
        }

        return null;
    }

    private static FacilityResponse ToResponse(Facility facility)
        => new(
            facility.Id,
            facility.Name,
            facility.CrmStatus,
            facility.FacilityType,
            facility.Website,
            facility.Street,
            facility.PostalCode,
            facility.City,
            facility.Country,
            facility.BillingStreet,
            facility.BillingPostalCode,
            facility.BillingCity,
            facility.BillingCountry,
            facility.FollowUpDueDate,
            facility.BillingRate,
            facility.NightSurchargePercent,
            facility.SaturdaySurchargePercent,
            facility.SundaySurchargePercent,
            facility.HolidaySurchargePercent,
            facility.TravelCostRate,
            facility.TravelCostMode,
            facility.TravelCostPerKm,
            facility.MealAllowanceRate,
            facility.MinimumHours,
            facility.BillingInterval,
            facility.PaymentTermDays,
            facility.IndividualAgreements);
}
