using System.Text.Json;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Meezi.API.Models.Public;
using Meezi.API.Services;
using Meezi.Core.Discover;
using Meezi.Core.Interfaces;
using Meezi.Infrastructure.Data;
using Meezi.Shared;
using Microsoft.EntityFrameworkCore;

namespace Meezi.API.Controllers;

/// <summary>
/// Allows cafe owners to manage their public-facing profile:
/// description, gallery (up to 8 photos), working hours, instagram, website.
/// Route: /api/cafes/{cafeId}/public-profile
/// </summary>
[Authorize]
[Route("api/cafes/{cafeId}/public-profile")]
public class CafePublicProfileController : CafeApiControllerBase
{
    private const int MaxGalleryPhotos = 8;

    private readonly AppDbContext _db;
    private readonly IMediaStorageService _media;

    private static readonly JsonSerializerOptions _jsonOpts = new()
    {
        PropertyNameCaseInsensitive = true,
        PropertyNamingPolicy = JsonNamingPolicy.CamelCase
    };

    public CafePublicProfileController(AppDbContext db, IMediaStorageService media)
    {
        _db    = db;
        _media = media;
    }

    // ── GET ──────────────────────────────────────────────────────────────────

    [HttpGet]
    public async Task<IActionResult> Get(string cafeId, ITenantContext tenant, CancellationToken ct)
    {
        if (EnsureCafeAccess(cafeId, tenant) is { } denied) return denied;

        var cafe = await _db.Cafes.AsNoTracking()
            .FirstOrDefaultAsync(c => c.Id == cafeId, ct);
        if (cafe is null)
            return NotFound(Fail("NOT_FOUND", "Cafe not found."));

        var hours   = Deserialize<WorkingHoursSchedule>(cafe.WorkingHoursJson);
        var gallery = Deserialize<List<string>>(cafe.GalleryJson) ?? [];

        return Ok(new ApiResponse<CafeProfileEditDto>(true, new CafeProfileEditDto(
            cafe.Description,
            gallery,
            cafe.InstagramHandle,
            cafe.WebsiteUrl,
            ToHoursDto(hours))));
    }

    // ── PUT (description / social / hours) ───────────────────────────────────

    [HttpPut]
    public async Task<IActionResult> Put(
        string cafeId,
        [FromBody] UpdateCafePublicProfileRequest request,
        ITenantContext tenant,
        CancellationToken ct)
    {
        if (EnsureCafeAccess(cafeId, tenant) is { } denied) return denied;

        var cafe = await _db.Cafes.FirstOrDefaultAsync(c => c.Id == cafeId, ct);
        if (cafe is null)
            return NotFound(Fail("NOT_FOUND", "Cafe not found."));

        // Description
        if (request.Description is not null)
            cafe.Description = request.Description.Trim();

        // Instagram handle — strip leading @ if present
        if (request.InstagramHandle is not null)
            cafe.InstagramHandle = request.InstagramHandle.TrimStart('@').Trim().ToLowerInvariant();

        // Website URL
        if (request.WebsiteUrl is not null)
            cafe.WebsiteUrl = request.WebsiteUrl.Trim();

        // Working hours
        if (request.WorkingHours is not null)
            cafe.WorkingHoursJson = JsonSerializer.Serialize(ToHoursSchedule(request.WorkingHours), _jsonOpts);

        await _db.SaveChangesAsync(ct);

        var gallery = Deserialize<List<string>>(cafe.GalleryJson) ?? [];
        var hours   = Deserialize<WorkingHoursSchedule>(cafe.WorkingHoursJson);

        return Ok(new ApiResponse<CafeProfileEditDto>(true, new CafeProfileEditDto(
            cafe.Description,
            gallery,
            cafe.InstagramHandle,
            cafe.WebsiteUrl,
            ToHoursDto(hours))));
    }

    // ── POST gallery/upload ───────────────────────────────────────────────────

    [HttpPost("gallery")]
    [RequestSizeLimit(8 * 1024 * 1024)]
    public async Task<IActionResult> UploadGalleryPhoto(
        string cafeId,
        [FromForm] IFormFile photo,
        ITenantContext tenant,
        CancellationToken ct)
    {
        if (EnsureCafeAccess(cafeId, tenant) is { } denied) return denied;

        if (photo is null || photo.Length == 0)
            return BadRequest(Fail("NO_FILE", "No photo provided."));

        var cafe = await _db.Cafes.FirstOrDefaultAsync(c => c.Id == cafeId, ct);
        if (cafe is null)
            return NotFound(Fail("NOT_FOUND", "Cafe not found."));

        var gallery = Deserialize<List<string>>(cafe.GalleryJson) ?? [];

        if (gallery.Count >= MaxGalleryPhotos)
            return BadRequest(Fail("GALLERY_FULL", $"Maximum {MaxGalleryPhotos} gallery photos allowed. Remove one first."));

        var url = await _media.SaveCafeGalleryPhotoAsync(cafeId, photo, ct);
        if (url is null)
            return StatusCode(422, Fail("UPLOAD_FAILED", "Could not process image. Check format and size (max 5 MB, JPEG/PNG/WebP)."));

        gallery.Add(url);
        cafe.GalleryJson = JsonSerializer.Serialize(gallery, _jsonOpts);
        await _db.SaveChangesAsync(ct);

        return Ok(new ApiResponse<GalleryDto>(true, new GalleryDto(gallery)));
    }

    // ── DELETE gallery photo ──────────────────────────────────────────────────

    [HttpDelete("gallery")]
    public async Task<IActionResult> RemoveGalleryPhoto(
        string cafeId,
        [FromQuery] string url,
        ITenantContext tenant,
        CancellationToken ct)
    {
        if (EnsureCafeAccess(cafeId, tenant) is { } denied) return denied;

        if (string.IsNullOrWhiteSpace(url))
            return BadRequest(Fail("NO_URL", "Provide ?url= of the photo to remove."));

        var cafe = await _db.Cafes.FirstOrDefaultAsync(c => c.Id == cafeId, ct);
        if (cafe is null)
            return NotFound(Fail("NOT_FOUND", "Cafe not found."));

        var gallery = Deserialize<List<string>>(cafe.GalleryJson) ?? [];
        var removed = gallery.Remove(url);

        if (!removed)
            return NotFound(Fail("NOT_IN_GALLERY", "URL not found in gallery."));

        cafe.GalleryJson = JsonSerializer.Serialize(gallery, _jsonOpts);
        await _db.SaveChangesAsync(ct);

        return Ok(new ApiResponse<GalleryDto>(true, new GalleryDto(gallery)));
    }

    // ── helpers ───────────────────────────────────────────────────────────────

    private static ApiResponse<object> Fail(string code, string message) =>
        new(false, null, new ApiError(code, message));

    private static T? Deserialize<T>(string? json) where T : class
    {
        if (string.IsNullOrWhiteSpace(json)) return null;
        try { return JsonSerializer.Deserialize<T>(json, _jsonOpts); }
        catch { return null; }
    }

    private static WorkingHoursPublicDto? ToHoursDto(WorkingHoursSchedule? h)
    {
        if (h is null) return null;
        DaySchedulePublicDto? M(DaySchedule? d) =>
            d is null ? null : new DaySchedulePublicDto(d.IsOpen, d.Open, d.Close);
        return new WorkingHoursPublicDto(M(h.Sat), M(h.Sun), M(h.Mon), M(h.Tue), M(h.Wed), M(h.Thu), M(h.Fri));
    }

    private static WorkingHoursSchedule ToHoursSchedule(WorkingHoursPublicDto dto)
    {
        static DaySchedule? M(DaySchedulePublicDto? d) =>
            d is null ? null : new DaySchedule { IsOpen = d.IsOpen, Open = d.Open, Close = d.Close };
        return new WorkingHoursSchedule
        {
            Sat = M(dto.Sat), Sun = M(dto.Sun), Mon = M(dto.Mon),
            Tue = M(dto.Tue), Wed = M(dto.Wed), Thu = M(dto.Thu), Fri = M(dto.Fri)
        };
    }
}

// ── Request / response DTOs ───────────────────────────────────────────────────

public record UpdateCafePublicProfileRequest(
    string? Description,
    string? InstagramHandle,
    string? WebsiteUrl,
    WorkingHoursPublicDto? WorkingHours);

public record CafeProfileEditDto(
    string? Description,
    IReadOnlyList<string> GalleryUrls,
    string? InstagramHandle,
    string? WebsiteUrl,
    WorkingHoursPublicDto? WorkingHours);

public record GalleryDto(IReadOnlyList<string> GalleryUrls);
