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

namespace Blahblah.FlowerStory.Server.Controller;

/// <summary>
/// 基础服务抽象类
/// </summary>
public abstract class BaseController : ControllerBase
{
    private const string Salt = "Blah blah, o! Flower story, intimately help you record every bit of the garden.";

    /// <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;
    /// <summary>
    /// 日志对象
    /// </summary>
    protected readonly ILogger<BaseController>? logger;

    /// <summary>
    /// 构造基础服务类
    /// </summary>
    /// <param name="database">数据库对象</param>
    /// <param name="logger">日志对象</param>
    protected BaseController(FlowerDatabase database, ILogger<BaseController>? logger = null)
    {
        this.database = database;
        this.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>
    protected string HashPassword(string password, string id)
    {
        if (string.IsNullOrEmpty(password))
        {
            throw new ArgumentNullException(nameof(password));
        }
        if (string.IsNullOrEmpty(id))
        {
            throw new ArgumentNullException(nameof(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 = database.Users.Find(token.UserId);
        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;
            }
            database.Tokens.Update(token);

            user.ActiveDateUnixTime = now.ToUnixTimeMilliseconds();
            database.Users.Update(user);
        }

        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;
    }
}