using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Monitoring.Common;
using Monitoring.Controllers;
using Monitoring.Core.Domain.CameraManagement;
using Monitoring.Core.Domain.EventManagement;
using Monitoring.Extensions;
using Monitoring.Features.CameraViews.CreateCameraView;
using Monitoring.Features.CameraViews.GetCameraViewList;
using Monitoring.Features.CameraViews.UpdateCameraView;
using Monitoring.Infrastructure.Data;
using Monitoring.Models;

namespace Monitoring.Features.CameraViews;

[Route("api/camera-views")]
[Authorize(Roles = $"{Roles.Manager},{Roles.Watcher}")]
public class CameraViewsController(DataContext dataContext) : BaseController
{
	[HttpGet(Name = "GetCameraViewList")]
	[ProducesResponseType<TableDataResponseModel<CameraViewModel>>(StatusCodes.Status200OK)]
	public async Task<IActionResult> GetList(
		[FromQuery] GetCameraViewListRequest request,
		CancellationToken cancellationToken
	)
	{
		var query = dataContext
			.CameraViews.Include(v => v.Camera)
			.ThenInclude(c => c.Asset)
			.Where(v => v.Camera.Asset.Organization.Members.Any(m => m.UserId == UserId));

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

		if (request.CameraId.HasValue)
		{
			var cameraId = new CameraId(request.CameraId.Value);
			query = query.Where(v => v.CameraId == cameraId);
		}
		if (request.AlarmId.HasValue)
		{
			var alarmId = new AlarmId(request.AlarmId.Value);
			query = query.Where(v => v.AlarmId == alarmId);
		}
		if (request.From.HasValue)
		{
			query = query.Where(v => v.DateTime >= request.From.Value);
		}
		if (request.To.HasValue)
		{
			query = query.Where(v => v.DateTime <= request.To.Value);
		}

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

		return Ok(result);
	}

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

		var model = await dataContext
			.CameraViews.Where(v => v.Id == viewId)
			.Select(CameraViewModel.FromEntity)
			.FirstAsync(cancellationToken);
		return Ok(model);
	}

	[HttpPost(Name = "CreateCameraView")]
	[Authorize(Roles = Roles.Watcher)]
	[ProducesResponseType<string>(StatusCodes.Status404NotFound)]
	[ProducesResponseType<string>(StatusCodes.Status409Conflict)]
	[ProducesResponseType<CameraViewModel>(StatusCodes.Status200OK)]
	public async Task<IActionResult> Create(
		CreateCameraViewRequest request,
		CancellationToken cancellationToken
	)
	{
		var cameraId = new CameraId(request.CameraId);
		var alarmId = new AlarmId(request.AlarmId);

		var camera = await dataContext
			.Cameras.Include(c => c.Asset)
			.FirstOrDefaultAsync(
				c =>
					c.Id == cameraId
					&& c.Asset.Groups.Any(g => g.Accesses.Any(a => a.Member.UserId == UserId)),
				cancellationToken
			);
		if (camera is null)
		{
			return NotFound("Camera not found");
		}

		var alarm = await dataContext
			.Alarms.Include(a => a.Event)
			.ThenInclude(e => e.Asset)
			.FirstOrDefaultAsync(
				a =>
					a.Id == alarmId
					&& a.Event.Asset.Groups.Any(g =>
						g.Accesses.Any(acc => acc.Member.UserId == UserId)
					),
				cancellationToken
			);
		if (alarm is null)
		{
			return NotFound("Alarm not found");
		}

		if (alarm.Event.AssetId != camera.AssetId)
		{
			return Conflict("Alarm belongs to another asset");
		}

		var view = CameraView.Create(
			UserId,
			cameraId,
			alarmId,
			request.DateTime,
			request.StartVideo,
			request.EndVideo
		);

		await dataContext.CameraViews.AddAsync(view, cancellationToken);
		await dataContext.SaveChangesAsync(cancellationToken);

		var model = await dataContext
			.CameraViews.Where(v => v.Id == view.Id)
			.Select(CameraViewModel.FromEntity)
			.FirstAsync(cancellationToken);
		return Ok(model);
	}

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

		view.UpdateTimes(request.DateTime, request.StartVideo, request.EndVideo);

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

	[HttpDelete("{id:guid}", Name = "DeleteCameraView")]
	[Authorize(Roles = Roles.Manager)]
	[ProducesResponseType<string>(StatusCodes.Status404NotFound)]
	[ProducesResponseType(StatusCodes.Status204NoContent)]
	public async Task<IActionResult> Delete(Guid id, CancellationToken cancellationToken)
	{
		var viewId = new CameraViewId(id);
		var view = await dataContext
			.CameraViews.Include(v => v.Camera)
			.ThenInclude(c => c.Asset)
			.ThenInclude(a => a.Organization)
			.FirstOrDefaultAsync(
				v =>
					v.Id == viewId
					&& v.Camera.Asset.Organization.Members.Any(m => m.UserId == UserId),
				cancellationToken
			);
		if (view is null)
		{
			return NotFound("CameraView not found");
		}

		dataContext.CameraViews.Remove(view);
		await dataContext.SaveChangesAsync(cancellationToken);
		return NoContent();
	}
}
