using FluentValidation;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Monitoring.Common;
using Monitoring.Controllers;
using Monitoring.Core.Domain.AssetManagement;
using Monitoring.Core.Domain.CameraManagement;
using Monitoring.Extensions;
using Monitoring.Features.Cameras.CreateCamera;
using Monitoring.Features.Cameras.GetCameraList;
using Monitoring.Features.Cameras.UpdateCamera;
using Monitoring.Infrastructure.Data;
using Monitoring.Models;

namespace Monitoring.Features.Cameras;

[Route("api/cameras")]
[Authorize(Roles = $"{Roles.Manager},{Roles.Watcher}")]
public class CamerasController(DataContext dataContext) : BaseController
{
	[HttpGet(Name = "GetCameraList")]
	[ProducesResponseType<TableDataResponseModel<CameraModel>>(StatusCodes.Status200OK)]
	public async Task<IActionResult> GetList(
		[FromQuery] GetCameraListRequest request,
		CancellationToken cancellationToken
	)
	{
		var query = dataContext
			.Cameras.Include(c => c.Asset)
			.Where(c => c.Asset.Organization.Members.Any(m => m.UserId == UserId));

		if (Role == Roles.Watcher)
		{
			query = query.Where(c =>
				c.Asset.Groups.Any(g => g.Accesses.Any(a => a.Member.UserId == UserId))
			);
		}

		if (request.AssetId.HasValue)
		{
			var assetId = new AssetId(request.AssetId.Value);
			query = query.Where(c => c.AssetId == assetId);
		}

		if (!string.IsNullOrWhiteSpace(request.Search))
		{
			var search = request.Search.Trim().ToLowerInvariant();
			query = query.Where(c =>
				c.Name.Value.ToLower().Contains(search)
				|| c.Address.Value.ToLower().Contains(search)
			);
		}

		var result = await query
			.AsNoTracking()
			.Select(CameraModel.FromEntity)
			.GetTable(request, cancellationToken);

		return Ok(result);
	}

	[HttpGet("{id:guid}", Name = "GetCamera")]
	[ProducesResponseType<string>(StatusCodes.Status404NotFound)]
	[ProducesResponseType<CameraModel>(StatusCodes.Status200OK)]
	public async Task<IActionResult> Get(Guid id, CancellationToken cancellationToken)
	{
		var cameraId = new CameraId(id);
		var camera = await dataContext
			.Cameras.Include(c => c.Asset)
			.Where(c => c.Id == cameraId)
			.Where(c => c.Asset.Organization.Members.Any(m => m.UserId == UserId))
			.FirstOrDefaultAsync(cancellationToken);
		if (camera is null)
		{
			return NotFound("Camera not found");
		}
		if (Role == Roles.Watcher)
		{
			var hasAccess = await dataContext
				.Cameras.Where(c => c.Id == cameraId)
				.AnyAsync(
					c => c.Asset.Groups.Any(g => g.Accesses.Any(a => a.Member.UserId == UserId)),
					cancellationToken
				);
			if (!hasAccess)
			{
				return NotFound("Camera not found");
			}
		}

		var model = await dataContext
			.Cameras.Where(c => c.Id == cameraId)
			.Select(CameraModel.FromEntity)
			.FirstAsync(cancellationToken);
		return Ok(model);
	}

	[HttpPost(Name = "CreateCamera")]
	[Authorize(Roles = Roles.Manager)]
	[ProducesResponseType<string>(StatusCodes.Status404NotFound)]
	[ProducesResponseType<string>(StatusCodes.Status409Conflict)]
	[ProducesResponseType<CameraModel>(StatusCodes.Status200OK)]
	public async Task<IActionResult> Create(
		CreateCameraRequest request,
		CancellationToken cancellationToken
	)
	{
		var assetId = new AssetId(request.AssetId);
		var asset = await dataContext
			.Assets.Include(a => a.Organization)
			.FirstOrDefaultAsync(
				a => a.Id == assetId && a.Organization.Members.Any(m => m.UserId == UserId),
				cancellationToken
			);
		if (asset is null)
		{
			return NotFound("Asset not found");
		}

		var name = CameraName.Create(request.Name);
		var address = CameraAddress.Create(request.Address);

		var exists = await dataContext.Cameras.AnyAsync(
			c => c.AssetId == assetId && c.Name == name,
			cancellationToken
		);
		if (exists)
		{
			return Conflict("Camera with the same name already exists in the asset");
		}

		var camera = Camera.Create(name, address, assetId);
		await dataContext.Cameras.AddAsync(camera, cancellationToken);
		await dataContext.SaveChangesAsync(cancellationToken);

		var model = await dataContext
			.Cameras.Where(c => c.Id == camera.Id)
			.Select(CameraModel.FromEntity)
			.FirstAsync(cancellationToken);
		return Ok(model);
	}

	[HttpPut("{id:guid}", Name = "UpdateCamera")]
	[Authorize(Roles = Roles.Manager)]
	[ProducesResponseType<string>(StatusCodes.Status404NotFound)]
	[ProducesResponseType<string>(StatusCodes.Status409Conflict)]
	[ProducesResponseType(StatusCodes.Status204NoContent)]
	public async Task<IActionResult> Update(
		Guid id,
		UpdateCameraRequest request,
		CancellationToken cancellationToken
	)
	{
		var cameraId = new CameraId(id);
		var camera = await dataContext
			.Cameras.Include(c => c.Asset)
			.ThenInclude(a => a.Organization)
			.FirstOrDefaultAsync(
				c => c.Id == cameraId && c.Asset.Organization.Members.Any(m => m.UserId == UserId),
				cancellationToken
			);
		if (camera is null)
		{
			return NotFound("Camera not found");
		}

		var newName = CameraName.Create(request.Name);
		// Проверяем конфликт имен в рамках того же объекта (другая камера с таким же именем)
		var nameConflict = await dataContext.Cameras.AnyAsync(
			c => c.AssetId == camera.AssetId && c.Name == newName && c.Id != camera.Id,
			cancellationToken
		);
		if (nameConflict)
		{
			return Conflict("Camera with the same name already exists in the asset");
		}

		camera.Name = newName;
		camera.Address = CameraAddress.Create(request.Address);

		await dataContext.SaveChangesAsync(cancellationToken);
		return NoContent();
	}

	[HttpDelete("{id:guid}", Name = "DeleteCamera")]
	[Authorize(Roles = Roles.Manager)]
	[ProducesResponseType<string>(StatusCodes.Status404NotFound)]
	[ProducesResponseType(StatusCodes.Status204NoContent)]
	public async Task<IActionResult> Delete(Guid id, CancellationToken cancellationToken)
	{
		var cameraId = new CameraId(id);
		var camera = await dataContext.Cameras.FirstOrDefaultAsync(
			c => c.Id == cameraId && c.Asset.Organization.Members.Any(m => m.UserId == UserId),
			cancellationToken
		);
		if (camera is null)
		{
			return NotFound("Camera not found");
		}

		dataContext.Cameras.Remove(camera);
		await dataContext.SaveChangesAsync(cancellationToken);
		return NoContent();
	}
}
