using Blahblah.FlowerStory.Server.Data;
using Blahblah.FlowerStory.Server.Data.Model;
using Microsoft.AspNetCore.Mvc;
using SkiaSharp;
using System.Net;
using System.Reflection;
using System.Security.Cryptography;
using System.Text;

namespace Blahblah.FlowerStory.Server.Controller;

/// <summary>
/// 基础服务抽象类
/// </summary>
/// <remarks>
/// 构造基础服务类
/// </remarks>
/// <param name="database">数据库对象</param>
/// <param name="logger">日志对象</param>
public abstract partial class BaseController<T>(FlowerDatabase database, ILogger<T>? logger = null) : ControllerBase
{
    private const string Salt = "Blah blah, o! Flower story, intimately help you record every bit of the garden.";

    private const int ThumbWidth = 600;

    /// <summary>
    /// 临时 id
    /// </summary>
    protected const int TempId = -1;

    private static string? uploadsDirectory;
    /// <summary>
    /// 文件上传路径
    /// </summary>
    protected static string UploadsDirectory => uploadsDirectory ??= Path.Combine(Program.DataPath, "uploads");

    /// <summary>
    /// 支持的图片文件签名
    /// </summary>
    protected static readonly List<byte[]> PhotoSignatures =
    [
        // jpeg
        [0xFF, 0xD8, 0xFF, 0xDB],
        [0xFF, 0xD8, 0xFF, 0xE0],
        [0xFF, 0xD8, 0xFF, 0xE1],
        [0xFF, 0xD8, 0xFF, 0xE2],
        [0xFF, 0xD8, 0xFF, 0xE3],
        // png
        [0x89, 0x50, 0x4E, 0x47]
    ];

    /// <summary>
    /// 自定义认证头的关键字
    /// </summary>
    protected const string AuthHeader = "Authorization";
    /// <summary>
    /// 禁用用户
    /// </summary>
    protected const int UserDisabled = -1;
    /// <summary>
    /// 普通用户
    /// </summary>
    protected const int UserCommon = 0;
    /// <summary>
    /// 管理员用户
    /// </summary>
    protected const int UserAdmin = 99;

    /// <summary>
    /// 数据库对象
    /// </summary>
    protected readonly FlowerDatabase database = database;
    /// <summary>
    /// 日志对象
    /// </summary>
    protected readonly ILogger? logger = logger;

    /// <summary>
    /// 计算密码的 hash
    /// </summary>
    /// <param name="password">密码原文</param>
    /// <param name="id">用户 id</param>
    /// <returns>密码 hash,值为 SHA256(password+id+salt)</returns>
    /// <exception cref="ArgumentNullException"></exception>
    public string HashPassword(string password, string id)
    {
        ArgumentException.ThrowIfNullOrEmpty(password);
        ArgumentException.ThrowIfNullOrEmpty(id);

        var data = Encoding.UTF8.GetBytes($"{password}{id}{Salt}");
        var hash = SHA256.HashData(data);
        return Convert.ToHexString(hash);
    }

    /// <summary>
    /// 检出当前会话
    /// </summary>
    /// <returns>若检查失败,第一个参数返回异常 ActionResult。第二个参数返回会话令牌对象</returns>
    protected (ActionResult? Result, TokenItem? Token) CheckToken()
    {
        if (!Request.Headers.TryGetValue(AuthHeader, out var h))
        {
            logger?.LogWarning("request with no {auth} header", AuthHeader);
            return (Unauthorized(), null);
        }
        string hash = h.ToString();
        var token = database.Tokens.Find(hash);
        if (token == null)
        {
            logger?.LogWarning("token \"{hash}\" not found", hash);
            return (Unauthorized(), null);
        }
        return (null, token);
    }

    /// <summary>
    /// 检查当前会话权限
    /// </summary>
    /// <param name="level">需要大于等于该权限,默认为 0 - UserCommon</param>
    /// <returns>若检查失败,第一个参数返回异常 ActionResult。第二个参数返回会话对应的用户对象</returns>
    protected (ActionResult? Result, UserItem? User) CheckPermission(int? level = 0)
    {
        var (result, token) = CheckToken();
        if (result != null)
        {
            return (result, null);
        }
        if (token == null)
        {
            return (Unauthorized(), null);
        }
        if (token.ExpireDate < DateTimeOffset.UtcNow)
        {
            logger?.LogWarning("token \"{hash}\" has expired after {date}", token.Id, token.ExpireDate);
            return (Unauthorized(), null);
        }
        var user = QueryUserItem(token.UserId);
        //var user = database.Users.Where(u => u.Id == token.UserId).Select(u => new UserItem
        //{
        //    Id = u.Id,
        //    UserId = u.UserId,
        //    Level = u.Level,
        //    RegisterDateUnixTime = u.RegisterDateUnixTime,
        //    ActiveDateUnixTime = u.ActiveDateUnixTime,
        //    Name = u.Name,
        //    Email = u.Email,
        //    Mobile = u.Mobile
        //}).SingleOrDefault();
        if (user == null)
        {
            logger?.LogWarning("user not found with id {id}", token.UserId);
        }
        else if (user.Level < level)
        {
            logger?.LogWarning("user \"{id}\" level ({level}) lower than required ({required})", user.UserId, user.Level, level);
            return (Forbid(), user);
        }
        else
        {
            var now = DateTimeOffset.UtcNow;
            token.ActiveDateUnixTime = now.ToUnixTimeMilliseconds();
            var expires = now.AddSeconds(token.ExpireSeconds).ToUnixTimeMilliseconds();
            if (expires > token.ExpireDateUnixTime)
            {
                token.ExpireDateUnixTime = expires;
            }

            user.ActiveDateUnixTime = now.ToUnixTimeMilliseconds();
        }

        return (null, user);
    }

    /// <summary>
    /// 保存数据库变动并输出日志
    /// </summary>
    /// <returns>操作的数据库行数</returns>
    protected int SaveDatabase()
    {
        var count = database.SaveChanges();
        if (count > 0)
        {
            logger?.LogInformation("{number} of entries written to database.", count);
        }
        else
        {
            logger?.LogWarning("no data written to database.");
        }
        return count;
    }

    /// <summary>
    /// 获取嵌入流的数据
    /// </summary>
    /// <param name="filename">文件名</param>
    /// <param name="namespace">命名空间,默认为程序集.wwwroot</param>
    /// <returns></returns>
    protected static byte[] GetEmbeddedData(string filename, string? @namespace = null)
    {
        Assembly asm = Assembly.GetExecutingAssembly();
        if (string.IsNullOrEmpty(@namespace))
        {
            @namespace = typeof(Program).Namespace + ".wwwroot";
        }
        using Stream? stream = asm.GetManifestResourceStream($"{@namespace}.{filename}");
        if (stream == null)
        {
            return [];
        }
        using var ms = new MemoryStream();
        stream.CopyTo(ms);
        return ms.ToArray();
    }

    /// <summary>
    /// 读取文件到 byte 数组
    /// </summary>
    /// <param name="file">来自请求的文件</param>
    /// <returns>文件结果对象</returns>
    protected static FileResult? WrapFormFile(IFormFile file)
    {
        if (file?.Length > 0)
        {
            using var stream = file.OpenReadStream();

            // check header
            var headers = new byte[PhotoSignatures.Max(s => s.Length)];
            int count = stream.Read(headers, 0, headers.Length);
            if (PhotoSignatures.Any(s => headers.Take(s.Length).SequenceEqual(s)))
            {
                using var ms = new MemoryStream();
                ms.Write(headers, 0, count);

#if __OLD_READER__
                // reading
                const int size = 16384;
                var buffer = new byte[size];
                while ((count = stream.Read(buffer, 0, size)) > 0)
                {
                    ms.Write(buffer, 0, count);
                }
#else
                stream.CopyTo(ms);
#endif
                var data = ms.ToArray();
                var name = file.FileName;
                var ext = Path.GetExtension(name);
                var path = $"{WebUtility.UrlEncode(Path.GetFileNameWithoutExtension(name))}_{DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()}{ext}";

                var image = SKImage.FromEncodedData(data);

                return new FileResult
                {
                    Filename = name,
                    FileType = ext,
                    Path = path,
                    Content = data,
                    Width = image.Width,
                    Height = image.Height
                };
            }
        }
        return null;
    }

    private static string GetFlowerDirectory(int fid, bool create = false)
    {
        var directory = Path.Combine(UploadsDirectory, fid.ToString());
        if (create && !Directory.Exists(directory))
        {
            Directory.CreateDirectory(directory);
        }
        return directory;
    }

    /// <summary>
    /// 创建缩略图
    /// </summary>
    /// <param name="data">图片数据</param>
    /// <param name="maxWidth">最大宽度</param>
    /// <param name="quality">缩放质量</param>
    /// <returns></returns>
    protected static byte[] CreateThumbnail(byte[] data, int maxWidth = ThumbWidth, SKFilterQuality quality = SKFilterQuality.Medium)
    {
        if (maxWidth <= 0)
        {
            return data;
        }
        using var image = SKImage.FromEncodedData(data);
        if (maxWidth >= image.Width)
        {
            using var enc = image.Encode(SKEncodedImageFormat.Jpeg, 80);
            return enc.ToArray();
        }
        var height = maxWidth * image.Height / image.Width;
        using var bitmap = SKBitmap.FromImage(image);
        using var resized = bitmap.Resize(new SKImageInfo(maxWidth, height), quality);
        using var encode = resized.Encode(SKEncodedImageFormat.Jpeg, 80);
        return encode.ToArray();
    }

    /// <summary>
    /// 写入文件到用户的花草目录中
    /// </summary>
    /// <param name="fid">花草唯一 id</param>
    /// <param name="file">文件对象</param>
    /// <param name="thumbnail">创建缩略图</param>
    /// <param name="token">取消令牌</param>
    protected async Task WriteToFile(int fid, FileResult file, bool thumbnail = true, CancellationToken token = default)
    {
        var directory = GetFlowerDirectory(fid, true);
        var path = Path.Combine(directory, file.Path);
        await System.IO.File.WriteAllBytesAsync(path, file.Content, token);

        if (thumbnail)
        {
            try
            {
                var thumb = CreateThumbnail(file.Content);
                path = Path.Combine(directory, $"{file.Path}.thumb");
                await System.IO.File.WriteAllBytesAsync(path, thumb, token);
            }
            catch (Exception ex)
            {
                logger?.LogError(ex, "failed to create thumbnail for flower: {fid}, file: {file}, error: {message}", fid, file.Filename, ex.Message);
            }
        }
    }

    /// <summary>
    /// 删除花草下的文件
    /// </summary>
    /// <param name="fid">花草唯一 id</param>
    /// <param name="path">文件路径</param>
    /// <returns>返回是否已删除</returns>
    protected static bool DeleteFile(int fid, string path)
    {
        var directory = GetFlowerDirectory(fid);
        if (Directory.Exists(directory))
        {
            path = Path.Combine(directory, path);
            if (System.IO.File.Exists(path))
            {
                System.IO.File.Delete(path);
                return true;
            }
        }
        return false;
    }

    /// <summary>
    /// 移动临时照片到花草目录
    /// </summary>
    /// <param name="fid">花草唯一 id</param>
    /// <param name="path">文件路径</param>
    protected static void MoveTempFileToFlower(int fid, string path)
    {
        var directory = GetFlowerDirectory(TempId);
        if (Directory.Exists(directory))
        {
            var file = Path.Combine(directory, path);
            if (System.IO.File.Exists(file))
            {
                directory = GetFlowerDirectory(fid, true);
                var to = Path.Combine(directory, path);
                if (System.IO.File.Exists(to))
                {
                    System.IO.File.Move(to, $"{to}.bak");
                }
                System.IO.File.Move(file, to);
            }
        }
    }

    private const double EarthRadius = 6378137;

    private static double Radius(double degree)
    {
        return degree * Math.PI / 180.0;
    }

    /// <summary>
    /// 获取两个经纬度之间的距离
    /// </summary>
    /// <param name="lat1">纬度1</param>
    /// <param name="lon1">经度1</param>
    /// <param name="lat2">纬度2</param>
    /// <param name="lon2">经度2</param>
    /// <returns></returns>
    protected static double GetDistance(double lat1, double lon1, double lat2, double lon2)
    {
        double rlat1 = Radius(lat1);
        double rlat2 = Radius(lat2);
        return EarthRadius * Math.Acos(
            Math.Cos(rlat1) * Math.Cos(rlat2) * Math.Cos(Radius(lon1) - Radius(lon2)) +
            Math.Sin(rlat1) * Math.Sin(rlat2));
    }

    /// <inheritdoc/>
    protected static double GetDistance2(double lat1, double lon1, double lat2, double lon2)
    {
        double rlat1 = Radius(lat1);
        double rlat2 = Radius(lat2);
        double a = rlat1 - rlat2;
        double b = Radius(lon1) - Radius(lon2);
        double s = 2 * Math.Asin(Math.Sqrt(Math.Pow(Math.Sin(a / 2), 2) + Math.Cos(rlat1) * Math.Cos(rlat2) * Math.Pow(Math.Sin(b / 2), 2)));
        return s * EarthRadius;
    }
}

/// <summary>
/// 文件结果
/// </summary>
public record FileResult
{
    /// <summary>
    /// 文件名
    /// </summary>
    public required string Filename { get; init; }

    /// <summary>
    /// 文件类型
    /// </summary>
    public required string FileType { get; init; }

    /// <summary>
    /// 储存路径
    /// </summary>
    public required string Path { get; init; }

    /// <summary>
    /// 文件内容
    /// </summary>
    public required byte[] Content { get; init; }

    /// <summary>
    /// 照片宽度
    /// </summary>
    public required int Width { get; init; }

    /// <summary>
    /// 照片高度
    /// </summary>
    public required int Height { get; init; }
}