using System;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using AngleSharp.Html.Dom;
using AngleSharp.Html.Parser;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

namespace Ksp.WebServer
{
    public class KspAuthenticator
    {
        readonly KspProxyConfig kspProxyConfig;
        readonly ILogger<KspAuthenticator> logger;

        // request to https://ksp.mff.cuni.cz/auth/manage.cgi?mode=change
        // contains user information

        public KspAuthenticator(
            IOptions<KspProxyConfig> kspProxyConfig,
            ILogger<KspAuthenticator> logger)
        {
            this.kspProxyConfig = kspProxyConfig.Value;
            this.logger = logger;
        }

        public static UnverifiedAuthCookie ParseAuthCookie(string cookie)
        {
            var s = cookie.Split(':');
            return new UnverifiedAuthCookie(
                UserId.Parse(s[1]),
                s[3],
                s[2].Split(',')
            );
        }

        async Task<IHtmlDocument> FetchPage(string url, string authCookie)
        {
            var cookies = new CookieContainer();
            cookies.Add(new Uri(kspProxyConfig.Host), new Cookie("ksp_auth", Uri.EscapeDataString(authCookie)));
            var c = new HttpClient(new HttpClientHandler { CookieContainer = cookies });
            var rq = new HttpRequestMessage(HttpMethod.Get, $"{kspProxyConfig.Host}/{url}");
            rq.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("text/html"));
            rq.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/xhtml+xml"));
            if (!string.IsNullOrEmpty(kspProxyConfig.Authorization))
                rq.Headers.Authorization =
                    AuthenticationHeaderValue.Parse(kspProxyConfig.Authorization);
            var rs = await c.SendAsync(rq);
            logger.LogInformation("Verification response {code}", rs.StatusCode);
            if (rs.StatusCode != HttpStatusCode.OK)
                return null;
            var response = await rs.Content.ReadAsStringAsync();
            var parser = new HtmlParser();
            return parser.ParseDocument(response);
        }

        public async Task<VerifiedUserInfo> VerifyUser(string cookie)
        {
            var parsedCookie = ParseAuthCookie(cookie);
            logger.LogInformation("Verifying user {uid}", parsedCookie.Id);
            var page = await FetchPage("auth/manage.cgi?mode=change", cookie);
            var form = page?.QuerySelector("#content form");
            var email = (form?.QuerySelector("input#email") as IHtmlInputElement)?.Value;
            var userName = (form?.QuerySelector("input#logname") as IHtmlInputElement)?.Value;
            var showName = (form?.QuerySelector("input#showname") as IHtmlInputElement)?.Value;
            if (string.IsNullOrEmpty(email) || string.IsNullOrEmpty(userName) || string.IsNullOrEmpty(showName))
            {
                logger.LogWarning("User {uid} verification failed: email={email}, userName={userName}, name={name}", parsedCookie.Id.Value, email, userName, showName);
                return null;
            }
            logger.LogWarning("User {uid} verified: email={email}, userName={userName}, name={name}", parsedCookie.Id.Value, email, userName, showName);
            return new VerifiedUserInfo(
                parsedCookie.Id,
                showName,
                parsedCookie.Roles,
                email,
                userName
            );
        }
    }

    public sealed class UserId
    {
        public int Value { get; }
        public UserId(int value)
        {
            this.Value = value;
        }

        public override bool Equals(object obj)
        {
            return obj is UserId id &&
                   Value == id.Value;
        }

        public override int GetHashCode()
        {
            return HashCode.Combine(Value);
        }

        public override string ToString() => $"uid[{Value}]";
        public static UserId Parse(string val)
        {
            var id = int.Parse(val);
            if (id <= 0) throw new Exception();
            return new UserId(id);
        }
    }

    // Cookie, for example 1603380331:1662:auth_master,org:Standa=20Luke=c5=a1:SIGNATURE
    public sealed class UnverifiedAuthCookie
    {
        public UserId Id { get; }
        public string FullName { get; }
        public string[] Roles { get; }
        public UnverifiedAuthCookie(UserId id, string fullName, string[] roles)
        {
            this.Id = id;
            this.FullName = fullName;
            this.Roles = roles;
        }
    }

    public sealed class VerifiedUserInfo
    {
        public UserId Id { get; }
        public string FullName { get; }
        public string[] Roles { get; }
        public string Email { get; }
        public string UserName { get; }

        public VerifiedUserInfo(UserId id, string fullName, string[] roles, string email, string userName)
        {
            this.Id = id;
            this.FullName = fullName;
            this.Roles = roles;
            this.Email = email;
            this.UserName = userName;
        }
    }
}