using OF_DL.Entities;
using Serilog;
using Microsoft.Playwright;

namespace OF_DL.Helpers;

public class AuthHelper
{
    private readonly string _userDataDir = Path.GetFullPath("chromium-data");

    private const string _initScriptsDirName = "chromium-scripts";

    private readonly BrowserTypeLaunchPersistentContextOptions _options = new()
    {
        Headless = false,
        Channel = "chromium",
        Args = ["--no-sandbox", "--disable-setuid-sandbox", "--disable-blink-features=AutomationControlled", "--disable-infobars"],
    };

    private readonly string[] _desiredCookies =
    [
        "auth_id",
        "sess"
    ];

    private const float LoginTimeout = 600000f;  // 10 minutes
    private const float FeedLoadTimeout = 60000f; // 1 minute

    public async Task SetupBrowser(bool runningInDocker)
    {
        if (runningInDocker)
        {
            Log.Information("Running in Docker. Disabling sandbox and GPU.");
            _options.Args = ["--no-sandbox", "--disable-setuid-sandbox", "--disable-gpu", "--disable-blink-features=AutomationControlled", "--disable-infobars"];
        }
        else
        {
            var exitCode = Microsoft.Playwright.Program.Main(new[] {"install", "--with-deps", "chromium"});
            if (exitCode != 0)
            {
                throw new Exception($"Playwright chromium download failed. Exited with code {exitCode}");
            }
        }
    }

    private async Task<string> GetBcToken(IPage page)
    {
        return await page.EvaluateAsync<string>("window.localStorage.getItem('bcTokenSha') || ''");
    }

    public async Task<Auth?> GetAuthFromBrowser(bool isDocker = false)
    {
        try
        {
            IBrowserContext? browser = null;
            try
            {
                var playwright = await Playwright.CreateAsync();
                browser = await playwright.Chromium.LaunchPersistentContextAsync(_userDataDir, _options);
            }
            catch (Exception e)
            {
                if (e.Message.Contains("An error occurred trying to start process") && Directory.Exists(_userDataDir))
                {
                    Log.Error("Failed to launch browser. Deleting chrome-data directory and trying again.");
                    Directory.Delete(_userDataDir, true);
                    IPlaywright playwright = await Playwright.CreateAsync();
                    browser = await playwright.Chromium.LaunchPersistentContextAsync(_userDataDir, _options);
                }
                else
                {
                    throw;
                }
            }

            if (browser == null)
            {
                throw new Exception("Could not get browser");
            }

            IPage? page = browser.Pages[0];

            if (page == null)
            {
                throw new Exception("Could not get page");
            }

            string exeDirectory = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location) ?? string.Empty;
            string initScriptsDir = Path.Combine(exeDirectory, _initScriptsDirName);
            if (Directory.Exists(initScriptsDir))
            {
                Log.Information("Loading init scripts from {initScriptsDir}", initScriptsDir);
                foreach (string initScript in Directory.GetFiles(initScriptsDir, "*.js"))
                {
                    Log.Debug("Loading init script {initScript}", initScript);
                    await page.AddInitScriptAsync(initScript);
                }
            }

            Log.Debug("Navigating to OnlyFans.");
            await page.GotoAsync("https://onlyfans.com");

            Log.Debug("Waiting for user to login");
            await page.WaitForSelectorAsync(".b-feed", new PageWaitForSelectorOptions { Timeout = LoginTimeout });
            Log.Debug("Feed element detected (user logged in)");

            await page.ReloadAsync();

            await page.WaitForNavigationAsync(new PageWaitForNavigationOptions {
                WaitUntil = WaitUntilState.DOMContentLoaded,
                Timeout = FeedLoadTimeout
            });
            Log.Debug("DOM loaded. Getting BC token and cookies ...");

            string xBc;
            try
            {
                xBc = await GetBcToken(page);
            }
            catch (Exception e)
            {
                await browser.CloseAsync();
                throw new Exception("Error getting bcToken");
            }

            Dictionary<string, string> mappedCookies = (await browser.CookiesAsync())
                .Where(cookie => cookie.Domain.Contains("onlyfans.com"))
                .ToDictionary(cookie => cookie.Name, cookie => cookie.Value);

            mappedCookies.TryGetValue("auth_id", out string? userId);
            if (userId == null)
            {
                await browser.CloseAsync();
                throw new Exception("Could not find 'auth_id' cookie");
            }

            mappedCookies.TryGetValue("sess", out string? sess);
            if (sess == null)
            {
                await browser.CloseAsync();
                throw new Exception("Could not find 'sess' cookie");
            }

            string? userAgent = await page.EvaluateAsync<string>("navigator.userAgent");
            if (userAgent == null)
            {
                await browser.CloseAsync();
                throw new Exception("Could not get user agent");
            }

            string cookies = string.Join(" ", mappedCookies.Keys.Where(key => _desiredCookies.Contains(key))
                .Select(key => $"${key}={mappedCookies[key]};"));

            await browser.CloseAsync();

            return new Auth()
            {
                COOKIE = cookies,
                USER_AGENT = userAgent,
                USER_ID = userId,
                X_BC = xBc
            };
        }
        catch (Exception e)
        {
            Log.Error(e, "Error getting auth from browser");
            return null;
        }
    }
}
