using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.Owin;
using Microsoft.Owin;
using Microsoft.Owin.Host.SystemWeb;
using Microsoft.Owin.Infrastructure;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Cookies;
using Microsoft.Owin.Security.Google;
using Microsoft.Owin.Security.MicrosoftAccount;
using Owin;
using Owin.Security.Providers.GitHub;
using System;
using System.Diagnostics;
using System.Threading.Tasks;
using System.Web;
using WebApplication1.Models;

[assembly: OwinStartup(typeof(WebApplication1.Startup))]
namespace WebApplication1
{
    /// <summary>
    /// Custom cookie manager that forces Secure=true and SameSite=None on every
    /// cookie written through it. Used by providers whose options expose CookieManager
    /// (ApplicationCookie, Google, Microsoft). GitHub is handled separately below.
    /// </summary>
    public class SameSiteNoneCookieManager : ICookieManager
    {
        private readonly ICookieManager _inner;

        public SameSiteNoneCookieManager()
            : this(new SystemWebCookieManager()) { }

        public SameSiteNoneCookieManager(ICookieManager inner)
        {
            _inner = inner ?? throw new ArgumentNullException(nameof(inner));
        }

        public string GetRequestCookie(IOwinContext context, string key)
            => _inner.GetRequestCookie(context, key);

        public void AppendResponseCookie(IOwinContext context, string key, string value, CookieOptions options)
        {
            options.Secure = true;
            _inner.AppendResponseCookie(context, key, value, options);
            FixSameSite(context, key);
        }

        public void DeleteCookie(IOwinContext context, string key, CookieOptions options)
        {
            options.Secure = true;
            _inner.DeleteCookie(context, key, options);
            FixSameSite(context, key);
        }

        private static void FixSameSite(IOwinContext context, string key)
        {
            var httpContext = context.Get<HttpContextBase>(typeof(HttpContextBase).FullName);
            var cookie = httpContext?.Response?.Cookies[key];
            if (cookie != null)
                // FIX 1: fully qualified to resolve ambiguity with Microsoft.Owin.SameSiteMode
                cookie.SameSite = System.Web.SameSiteMode.None;
        }
    }

    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            // FIX 2: AddOnSendingHeaders middleware — safety net for Owin.Security.Providers.GitHub
            // which does NOT expose a CookieManager property. This fires right before ASP.NET
            // flushes the response and patches HttpResponseBase.Cookies (the typed collection
            // GitHub writes to) so the correlation cookie always gets Secure + SameSite=None.
            app.Use(async (context, next) =>
            {
                var httpContext = context.Get<HttpContextBase>(typeof(HttpContextBase).FullName);
                httpContext?.Response?.AddOnSendingHeaders(ctx =>
                {
                    var cookies = ctx.Response.Cookies;
                    for (int i = 0; i < cookies.Count; i++)
                    {
                        var cookie = cookies[i];
                        if (cookie.SameSite != System.Web.SameSiteMode.None)
                        {
                            cookie.SameSite = System.Web.SameSiteMode.None;
                            cookie.Secure = true;
                        }
                    }
                });
                await next();
            });

            // ---- PER-REQUEST CONTEXTS ----
            app.CreatePerOwinContext(ApplicationDbContext.Create);
            app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
            app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);

            var cookieManager = new SameSiteNoneCookieManager();

            // ---- COOKIE AUTH (local app sign in) ----
            app.UseCookieAuthentication(new CookieAuthenticationOptions
            {
                AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
                LoginPath = new PathString("/Account/Login"),
                CookieSecure = CookieSecureOption.Always,
                CookieManager = cookieManager,
                Provider = new CookieAuthenticationProvider
                {
                    OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
                        validateInterval: TimeSpan.FromMinutes(30),
                        regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
                }
            });

            // UseExternalSignInCookie registers ExternalCookie AND sets SignInAsAuthenticationType
            // which all external providers require in their constructors.
            app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);

            app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5));
            app.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);

            // ---- EXTERNAL PROVIDERS ----
            var googleId = System.Configuration.ConfigurationManager.AppSettings["GoogleClientId"];
            var googleSecret = System.Configuration.ConfigurationManager.AppSettings["GoogleClientSecret"];
            if (!string.IsNullOrWhiteSpace(googleId) && !string.IsNullOrWhiteSpace(googleSecret))
            {
                app.UseGoogleAuthentication(new GoogleOAuth2AuthenticationOptions
                {
                    ClientId = googleId,
                    ClientSecret = googleSecret,
                    CallbackPath = new PathString("/signin-google"),
                    CookieManager = cookieManager
                });
            }

            var msId = System.Configuration.ConfigurationManager.AppSettings["MicrosoftClientId"];
            var msSecret = System.Configuration.ConfigurationManager.AppSettings["MicrosoftClientSecret"];
            if (!string.IsNullOrWhiteSpace(msId) && !string.IsNullOrWhiteSpace(msSecret))
            {
                app.UseMicrosoftAccountAuthentication(new MicrosoftAccountAuthenticationOptions
                {
                    ClientId = msId,
                    ClientSecret = msSecret,
                    CallbackPath = new PathString("/signin-microsoft"),
                    CookieManager = cookieManager,
                    Provider = new MicrosoftAccountAuthenticationProvider
                    {
                        OnAuthenticated = ctx =>
                        {
                            Trace.WriteLine("Microsoft OnAuthenticated: token present? " + (ctx.AccessToken != null));
                            return Task.FromResult(0);
                        },
                        OnReturnEndpoint = ctx =>
                        {
                            if (ctx.Identity == null)
                                Trace.WriteLine("Microsoft OnReturnEndpoint: Identity is NULL");
                            return Task.FromResult(0);
                        },
                        OnApplyRedirect = ctx =>
                        {
                            Trace.WriteLine("Microsoft redirecting to: " + ctx.RedirectUri);
                            ctx.Response.Redirect(ctx.RedirectUri);
                        }
                    }
                });
            }

            var ghId = System.Configuration.ConfigurationManager.AppSettings["GitHubClientId"];
            var ghSecret = System.Configuration.ConfigurationManager.AppSettings["GitHubClientSecret"];
            if (!string.IsNullOrWhiteSpace(ghId) && !string.IsNullOrWhiteSpace(ghSecret))
            {
                // CookieManager intentionally omitted: Owin.Security.Providers.GitHub does not
                // expose that property. The AddOnSendingHeaders middleware at the top of this
                // method patches GitHub's correlation cookie before the response is flushed.
                app.UseGitHubAuthentication(new GitHubAuthenticationOptions
                {
                    ClientId = ghId,
                    ClientSecret = ghSecret,
                    CallbackPath = new PathString("/signin-github"),
                    Scope = { "user:email" },
                    Provider = new Owin.Security.Providers.GitHub.GitHubAuthenticationProvider
                    {
                        OnAuthenticated = ctx =>
                        {
                            Trace.WriteLine("GitHub OnAuthenticated: token present? " + (ctx.AccessToken != null));
                            return Task.FromResult(0);
                        },
                        OnReturnEndpoint = ctx =>
                        {
                            if (ctx.Identity == null)
                                Trace.WriteLine("GitHub OnReturnEndpoint: Identity is NULL");
                            return Task.FromResult(0);
                        }
                    }
                });
            }
        }
    }
}