using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using MiGu.Server.Ota;

namespace MiGu.Server.Controllers;

/// <summary>
/// WatchDog 回传包接收端。
/// WatchDog 写死 POST 到 http://{config.serverIP}:8000/upload-mdcs/{routeKey}，
/// 必须与参考 Electron Express :8000 路径一致；/api/ota/receive/* 仅作兼容别名。
/// </summary>
[ApiController]
[AllowAnonymous]
public class OtaReceiveController : ControllerBase
{
    private readonly OtaStore _store;
    private readonly ILogger<OtaReceiveController> _log;

    public OtaReceiveController(OtaStore store, ILogger<OtaReceiveController> log)
    {
        _store = store;
        _log = log;
    }

    [HttpGet("/hello")]
    [HttpGet("/api/ota/receive/hello")]
    public IActionResult Hello() => Ok("ok");

    /// <summary>WatchDog 官方路径：/upload-mdcs/{Medullaexe|...}</summary>
    [HttpPost("/upload-mdcs/{routeKey}")]
    [HttpPost("/api/ota/receive/upload-mdcs/{routeKey}")]
    [HttpPost("/api/ota/receive/upload-mdcs{routeKey}")]
    [RequestSizeLimit(512_000_000)]
    public Task<IActionResult> UploadMdcs(string routeKey, CancellationToken ct)
        => SaveAsync(routeKey, ct);

    [HttpPost("/upload-history/{routeKey}")]
    [HttpPost("/api/ota/receive/upload-history/{routeKey}")]
    [HttpPost("/api/ota/receive/upload-history{routeKey}")]
    [RequestSizeLimit(512_000_000)]
    public async Task<IActionResult> UploadHistory(string routeKey, CancellationToken ct)
    {
        var ip = HttpContext.Connection.RemoteIpAddress?.ToString() ?? "unknown";
        if (!_store.TryGetActivePullId(ip, out _))
        {
            _log.LogWarning("OTA history rejected without active pull session from {Ip}", ip);
            return BadRequest("no active pull session");
        }

        var day = DateTime.Now.ToString("yyyy-MM-dd");
        var dir = Path.Combine(_store.HistoryDir, day, SafeFileName(ip, "unknown"));
        Directory.CreateDirectory(dir);
        var file = await ReadFirstFileAsync(ct);
        if (file == null || file.Length == 0) return BadRequest("empty");
        // 净化文件名：routeKey / FileName 都可能含路径分隔符，必须 GetFileName 防穿越。
        var rawName = string.IsNullOrWhiteSpace(file.FileName) ? routeKey : file.FileName;
        var safeName = SafeFileName(rawName, "unnamed");
        var path = Path.Combine(dir, safeName);
        await using var fs = System.IO.File.Create(path);
        await file.CopyToAsync(fs, ct);
        _log.LogInformation("OTA history receive {Route} -> {Path} ({Len})", routeKey, path, file.Length);
        return Ok(new { ok = true });
    }

    private async Task<IActionResult> SaveAsync(string routeKey, CancellationToken ct)
    {
        try
        {
            var clientIp = HttpContext.Connection.RemoteIpAddress?.ToString();
            if (!_store.TryGetActivePullId(clientIp, out _))
            {
                _log.LogWarning("OTA mdcs rejected without active pull session from {Ip}", clientIp ?? "unknown");
                return BadRequest("no active pull session");
            }

            var file = await ReadFirstFileAsync(ct);
            if (file == null || file.Length == 0) return BadRequest("empty file");

            var dest = _store.ResolveReceivePath(routeKey, clientIp);
            Directory.CreateDirectory(Path.GetDirectoryName(dest)!);
            await using (var fs = System.IO.File.Create(dest))
                await file.CopyToAsync(fs, ct);
            _log.LogInformation("OTA mdcs receive {Route} -> {Dest} ({Len})", routeKey, dest, file.Length);
            return Ok(new { ok = true, path = dest });
        }
        catch (Exception ex)
        {
            _log.LogWarning(ex, "OTA receive failed {Route}", routeKey);
            return BadRequest(ex.Message);
        }
    }

    private async Task<IFormFile?> ReadFirstFileAsync(CancellationToken ct)
    {
        if (!Request.HasFormContentType) return null;
        var form = await Request.ReadFormAsync(ct);
        return form.Files.FirstOrDefault();
    }

    private static string SafeFileName(string raw, string fallback)
    {
        var safe = Path.GetFileName(raw);
        if (string.IsNullOrWhiteSpace(safe)) safe = fallback;
        foreach (var ch in Path.GetInvalidFileNameChars())
            safe = safe.Replace(ch, '_');
        return string.IsNullOrWhiteSpace(safe) ? fallback : safe;
    }
}
