using LibraryAPI.Data;
using LibraryAPI.DTOs;
using LibraryAPI.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;

namespace LibraryAPI.Controllers
{
    [ApiController]
    [Route("api/[controller]")]
    public class BorrowRecordsController : ControllerBase
    {
        private readonly LibraryDbContext _context;

        public BorrowRecordsController(LibraryDbContext context)
        {
            _context = context;
        }

        // GET: api/borrowrecords
        [HttpGet]
        public async Task<ActionResult<IEnumerable<BorrowRecordDto>>> GetBorrowRecords(
            [FromQuery] BorrowRecordFilterParams filters)
        {
            var query = _context.BorrowRecords
                .Include(br => br.Book)
                    .ThenInclude(b => b.Author)
                .AsQueryable();

            if (!string.IsNullOrWhiteSpace(filters.BorrowerName))
                query = query.Where(br => br.BorrowerName.Contains(filters.BorrowerName));

            if (!string.IsNullOrWhiteSpace(filters.BorrowerEmail))
                query = query.Where(br => br.BorrowerEmail.Contains(filters.BorrowerEmail));

            if (filters.BookId.HasValue)
                query = query.Where(br => br.BookId == filters.BookId.Value);

            if (filters.Status.HasValue)
                query = query.Where(br => br.Status == filters.Status.Value);

            if (filters.BorrowDateFrom.HasValue)
                query = query.Where(br => br.BorrowDate >= filters.BorrowDateFrom.Value);

            if (filters.BorrowDateTo.HasValue)
                query = query.Where(br => br.BorrowDate <= filters.BorrowDateTo.Value);

            if (filters.IsOverdue.HasValue && filters.IsOverdue.Value)
                query = query.Where(br =>
                    br.Status == BorrowStatus.Active && br.DueDate < DateTime.UtcNow);

            var records = await query
                .OrderByDescending(br => br.BorrowDate)
                .Select(br => new BorrowRecordDto
                {
                    Id = br.Id,
                    BorrowerName = br.BorrowerName,
                    BorrowerEmail = br.BorrowerEmail,
                    BorrowDate = br.BorrowDate,
                    DueDate = br.DueDate,
                    ReturnDate = br.ReturnDate,
                    Status = br.Status.ToString(),
                    Notes = br.Notes,
                    BookId = br.BookId,
                    BookTitle = br.Book.Title,
                    BookISBN = br.Book.ISBN,
                    AuthorFullName = br.Book.Author.FirstName + " " + br.Book.Author.LastName
                })
                .ToListAsync();

            return Ok(records);
        }

        // GET: api/borrowrecords/5
        [HttpGet("{id}")]
        public async Task<ActionResult<BorrowRecordDto>> GetBorrowRecord(int id)
        {
            var record = await _context.BorrowRecords
                .Include(br => br.Book)
                    .ThenInclude(b => b.Author)
                .FirstOrDefaultAsync(br => br.Id == id);

            if (record == null)
                return NotFound(new { message = $"Borrow record with ID {id} not found." });

            return Ok(MapToDto(record));
        }

        // POST: api/borrowrecords
        [HttpPost]
        public async Task<ActionResult<BorrowRecordDto>> CreateBorrowRecord([FromBody] BorrowRecordCreateDto dto)
        {
            if (!ModelState.IsValid)
                return BadRequest(ModelState);

            // Validate DueDate is after BorrowDate
            if (dto.DueDate <= dto.BorrowDate)
                return BadRequest(new { message = "Due date must be after borrow date." });

            // Validate book exists and has available copies
            var book = await _context.Books
                .Include(b => b.Author)
                .FirstOrDefaultAsync(b => b.Id == dto.BookId);

            if (book == null)
                return BadRequest(new { message = $"Book with ID {dto.BookId} does not exist." });

            if (book.AvailableCopies <= 0)
                return Conflict(new { message = $"No available copies of '{book.Title}' at the moment." });

            // Decrement available copies
            book.AvailableCopies--;

            var record = new BorrowRecord
            {
                BorrowerName = dto.BorrowerName,
                BorrowerEmail = dto.BorrowerEmail,
                BorrowDate = dto.BorrowDate,
                DueDate = dto.DueDate,
                Notes = dto.Notes,
                Status = BorrowStatus.Active,
                BookId = dto.BookId
            };

            _context.BorrowRecords.Add(record);
            await _context.SaveChangesAsync();

            record.Book = book;
            return CreatedAtAction(nameof(GetBorrowRecord), new { id = record.Id }, MapToDto(record));
        }

        // PUT: api/borrowrecords/5
        [HttpPut("{id}")]
        public async Task<ActionResult<BorrowRecordDto>> UpdateBorrowRecord(int id, [FromBody] BorrowRecordUpdateDto dto)
        {
            if (!ModelState.IsValid)
                return BadRequest(ModelState);

            var record = await _context.BorrowRecords
                .Include(br => br.Book)
                    .ThenInclude(b => b.Author)
                .FirstOrDefaultAsync(br => br.Id == id);

            if (record == null)
                return NotFound(new { message = $"Borrow record with ID {id} not found." });

            var previousStatus = record.Status;

            // If status changes to Returned and no return date given, set to now
            if (dto.Status == BorrowStatus.Returned && dto.ReturnDate == null)
                dto.ReturnDate = DateTime.UtcNow;

            // Handle book availability adjustments
            if (previousStatus != BorrowStatus.Returned && dto.Status == BorrowStatus.Returned)
            {
                // Book is being returned — increment available copies
                record.Book.AvailableCopies++;
            }
            else if (previousStatus == BorrowStatus.Returned && dto.Status != BorrowStatus.Returned)
            {
                // Re-opening a returned record — decrement available copies again
                if (record.Book.AvailableCopies <= 0)
                    return Conflict(new { message = "Cannot reopen borrow record: no available copies." });
                record.Book.AvailableCopies--;
            }

            record.BorrowerName = dto.BorrowerName;
            record.BorrowerEmail = dto.BorrowerEmail;
            record.DueDate = dto.DueDate;
            record.ReturnDate = dto.ReturnDate;
            record.Status = dto.Status;
            record.Notes = dto.Notes;

            await _context.SaveChangesAsync();

            return Ok(MapToDto(record));
        }

        // DELETE: api/borrowrecords/5
        [HttpDelete("{id}")]
        public async Task<IActionResult> DeleteBorrowRecord(int id)
        {
            var record = await _context.BorrowRecords
                .Include(br => br.Book)
                .FirstOrDefaultAsync(br => br.Id == id);

            if (record == null)
                return NotFound(new { message = $"Borrow record with ID {id} not found." });

            if (record.Status == BorrowStatus.Active || record.Status == BorrowStatus.Overdue)
                return Conflict(new
                {
                    message = "Cannot delete an active or overdue borrow record. Mark it as returned first."
                });

            _context.BorrowRecords.Remove(record);
            await _context.SaveChangesAsync();

            return NoContent();
        }

        // ── Complex Feature #2 ────────────────────────────────────────────────
        // GET: api/borrowrecords/overdue-report
        // Returns all currently overdue records with full details across 3 tables
        [HttpGet("overdue-report")]
        public async Task<ActionResult<IEnumerable<OverdueReportItemDto>>> GetOverdueReport()
        {
            var now = DateTime.UtcNow;

            var overdueRecords = await _context.BorrowRecords
                .Include(br => br.Book)
                    .ThenInclude(b => b.Author)
                .Where(br => br.Status == BorrowStatus.Active && br.DueDate < now)
                .OrderByDescending(br => br.DueDate) // most overdue first
                .Select(br => new OverdueReportItemDto
                {
                    BorrowRecordId = br.Id,
                    BorrowerName = br.BorrowerName,
                    BorrowerEmail = br.BorrowerEmail,
                    BookId = br.BookId,
                    BookTitle = br.Book.Title,
                    AuthorFullName = br.Book.Author.FirstName + " " + br.Book.Author.LastName,
                    Genre = br.Book.Genre,
                    BorrowDate = br.BorrowDate,
                    DueDate = br.DueDate,
                    DaysOverdue = (int)((now - br.DueDate).TotalDays)
                })
                .ToListAsync();

            // Also mark those records as Overdue in the DB
            var ids = overdueRecords.Select(r => r.BorrowRecordId).ToList();
            await _context.BorrowRecords
                .Where(br => ids.Contains(br.Id))
                .ExecuteUpdateAsync(s => s.SetProperty(br => br.Status, BorrowStatus.Overdue));

            return Ok(overdueRecords);
        }

        // ── Helper ────────────────────────────────────────────────────────────

        private static BorrowRecordDto MapToDto(BorrowRecord br) => new BorrowRecordDto
        {
            Id = br.Id,
            BorrowerName = br.BorrowerName,
            BorrowerEmail = br.BorrowerEmail,
            BorrowDate = br.BorrowDate,
            DueDate = br.DueDate,
            ReturnDate = br.ReturnDate,
            Status = br.Status.ToString(),
            Notes = br.Notes,
            BookId = br.BookId,
            BookTitle = br.Book.Title,
            BookISBN = br.Book.ISBN,
            AuthorFullName = br.Book.Author.FirstName + " " + br.Book.Author.LastName
        };
    }
}
