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/contracts")]
public class ContractsController : ControllerBase
{
    private const string ContractTypeListKey = "ContractType";
    private const string ContractStatusListKey = "ContractStatus";

    private readonly IContractService _contractService;
    private readonly IValueListRepository _valueListRepository;
    private readonly IDomainEventDispatcher _dispatcher;

    public ContractsController(IContractService contractService, IValueListRepository valueListRepository, IDomainEventDispatcher dispatcher)
    {
        _contractService = contractService;
        _valueListRepository = valueListRepository;
        _dispatcher = dispatcher;
    }

    [HttpGet]
    [RequirePermission(ModuleType.Contracts, PermissionAction.View)]
    public async Task<ActionResult<PagedResponse<ContractResponse>>> GetAll(
        [FromQuery] string? search,
        [FromQuery] string? status,
        [FromQuery] Guid? employeeId,
        [FromQuery] Guid? facilityId,
        [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 _contractService.GetPagedAsync(search, status, employeeId, facilityId, page, pageSize, cancellationToken);
        return Ok(new PagedResponse<ContractResponse>(items.Select(ToResponse).ToList(), totalCount, page, pageSize));
    }

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

    [HttpPost]
    [RequirePermission(ModuleType.Contracts, PermissionAction.Create)]
    public async Task<ActionResult<ContractResponse>> Create(CreateContractRequest request, CancellationToken cancellationToken)
    {
        var fieldError = await ValidateFieldsAsync(
            request.ContractType,
            request.EmployeeId,
            request.FacilityId,
            request.StartDate,
            request.EndDate,
            request.WeeklyHours,
            request.HourlyWage,
            request.AllowancesDescription,
            request.OvertimeRules,
            request.VacationDaysPerYear,
            request.ProbationPeriodMonths,
            cancellationToken);
        if (fieldError is not null)
        {
            return BadRequest(fieldError);
        }

        var contract = new Contract
        {
            ContractType = request.ContractType,
            EmployeeId = request.EmployeeId,
            FacilityId = request.FacilityId,
            StartDate = request.StartDate,
            EndDate = request.EndDate,
            WeeklyHours = request.WeeklyHours,
            HourlyWage = request.HourlyWage,
            AllowancesDescription = request.AllowancesDescription,
            OvertimeRules = request.OvertimeRules,
            VacationDaysPerYear = request.VacationDaysPerYear,
            ProbationPeriodMonths = request.ProbationPeriodMonths
        };

        var created = await _contractService.CreateAsync(contract, cancellationToken);
        await _dispatcher.DispatchAsync(new ContractCreatedEvent(created.Id), cancellationToken);

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

    [HttpPut("{id:guid}")]
    [RequirePermission(ModuleType.Contracts, PermissionAction.Edit)]
    public async Task<ActionResult<ContractResponse>> Update(Guid id, UpdateContractRequest request, CancellationToken cancellationToken)
    {
        var fieldError = await ValidateFieldsAsync(
            request.ContractType,
            request.EmployeeId,
            request.FacilityId,
            request.StartDate,
            request.EndDate,
            request.WeeklyHours,
            request.HourlyWage,
            request.AllowancesDescription,
            request.OvertimeRules,
            request.VacationDaysPerYear,
            request.ProbationPeriodMonths,
            cancellationToken);
        if (fieldError is not null)
        {
            return BadRequest(fieldError);
        }

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

        var allowedStatuses = await _valueListRepository.GetActiveValuesAsync(ContractStatusListKey, cancellationToken);
        if (!allowedStatuses.Contains(request.Status))
        {
            return BadRequest($"Status muss einer der folgenden Werte sein: {string.Join(", ", allowedStatuses)}.");
        }

        var updates = new Contract
        {
            ContractType = request.ContractType,
            EmployeeId = request.EmployeeId,
            FacilityId = request.FacilityId,
            StartDate = request.StartDate,
            EndDate = request.EndDate,
            Status = request.Status,
            WeeklyHours = request.WeeklyHours,
            HourlyWage = request.HourlyWage,
            AllowancesDescription = request.AllowancesDescription,
            OvertimeRules = request.OvertimeRules,
            VacationDaysPerYear = request.VacationDaysPerYear,
            ProbationPeriodMonths = request.ProbationPeriodMonths
        };

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

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

    private async Task<string?> ValidateFieldsAsync(
        string contractType,
        Guid? employeeId,
        Guid? facilityId,
        DateOnly startDate,
        DateOnly? endDate,
        decimal? weeklyHours,
        decimal? hourlyWage,
        string? allowancesDescription,
        string? overtimeRules,
        int? vacationDaysPerYear,
        int? probationPeriodMonths,
        CancellationToken cancellationToken)
    {
        if (string.IsNullOrWhiteSpace(contractType) || contractType.Length > 100)
        {
            return "ContractType ist erforderlich und darf maximal 100 Zeichen lang sein.";
        }

        var allowedContractTypes = await _valueListRepository.GetActiveValuesAsync(ContractTypeListKey, cancellationToken);
        if (!allowedContractTypes.Contains(contractType))
        {
            return $"ContractType muss einer der folgenden Werte sein: {string.Join(", ", allowedContractTypes)}.";
        }

        if (employeeId is null && facilityId is null)
        {
            return "Ein Vertrag muss entweder einem Mitarbeiter oder einer Einrichtung zugeordnet sein.";
        }

        if (startDate == default)
        {
            return "StartDate ist erforderlich.";
        }

        if (endDate.HasValue && endDate.Value < startDate)
        {
            return "EndDate darf nicht vor StartDate liegen.";
        }

        if (weeklyHours.HasValue && weeklyHours.Value < 0)
        {
            return "WeeklyHours darf nicht negativ sein.";
        }

        if (hourlyWage.HasValue && hourlyWage.Value < 0)
        {
            return "HourlyWage darf nicht negativ sein.";
        }

        if (vacationDaysPerYear.HasValue && vacationDaysPerYear.Value < 0)
        {
            return "VacationDaysPerYear darf nicht negativ sein.";
        }

        if (probationPeriodMonths.HasValue && probationPeriodMonths.Value < 0)
        {
            return "ProbationPeriodMonths darf nicht negativ sein.";
        }

        if (allowancesDescription is { Length: > 500 })
        {
            return "AllowancesDescription darf maximal 500 Zeichen lang sein.";
        }

        if (overtimeRules is { Length: > 500 })
        {
            return "OvertimeRules darf maximal 500 Zeichen lang sein.";
        }

        return null;
    }

    private static ContractResponse ToResponse(Contract contract)
        => new(
            contract.Id,
            contract.ContractType,
            contract.EmployeeId,
            contract.FacilityId,
            contract.StartDate,
            contract.EndDate,
            contract.Status,
            contract.WeeklyHours,
            contract.HourlyWage,
            contract.AllowancesDescription,
            contract.OvertimeRules,
            contract.VacationDaysPerYear,
            contract.ProbationPeriodMonths);
}
