﻿using System;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using UnityEngine;
using UnityEditor;
using FrameDoctor.Editor.Config;

namespace FrameDoctor.Editor.Logic
{
    // Handles OAuth authentication with FrameDoctor web service.
    // Only available in the direct-download version (FRAMEDOCTOR_CLOUD).
    public class AuthService : IDisposable
    {
        private const string TokenKey = "FrameDoctor_AuthToken";
        private const string TokenExpiryKey = "FrameDoctor_TokenExpiry";
        private const int ListenerPortMin = 49152;
        private const int ListenerPortMax = 65535;
        private const int AuthTimeoutSeconds = 300;

        private HttpListener _httpListener;
        private CancellationTokenSource _cancellationTokenSource;
        private bool _isDisposed;

        public bool IsAuthenticated
        {
            get
            {
                var token = GetToken();
                if (string.IsNullOrEmpty(token))
                    return false;

                var expiryTicks = EditorPrefs.GetString(TokenExpiryKey, "0");
                if (long.TryParse(expiryTicks, out var ticks))
                {
                    var expiry = new DateTime(ticks);
                    if (DateTime.UtcNow >= expiry)
                    {
                        ClearToken();
                        return false;
                    }
                }

                return true;
            }
        }

        public string GetToken()
        {
            return EditorPrefs.GetString(TokenKey, null);
        }

        public void SetToken(string token, DateTime expiry)
        {
            EditorPrefs.SetString(TokenKey, token);
            EditorPrefs.SetString(TokenExpiryKey, expiry.Ticks.ToString());
        }

        public void ClearToken()
        {
            EditorPrefs.DeleteKey(TokenKey);
            EditorPrefs.DeleteKey(TokenExpiryKey);
        }

        public void Logout()
        {
            ClearToken();
            FrameDoctorSettings.Log("[FrameDoctor] Logged out");
        }

        // Initiates OAuth login flow.
        // Opens browser to auth page and waits for callback with token.
        public async Task<bool> LoginAsync()
        {
            if (IsAuthenticated)
                return true;

            _cancellationTokenSource = new CancellationTokenSource();

            int port = FindAvailablePort();
            if (port == -1)
            {
                Debug.LogError("[FrameDoctor] Could not find available port for auth callback");
                return false;
            }

            try
            {
                _httpListener = new HttpListener();
                _httpListener.Prefixes.Add($"http://localhost:{port}/");
                _httpListener.Start();

                var authUrl = $"{FrameDoctorConstants.WebServiceUrl}{FrameDoctorConstants.AuthPath}?port={port}";
                FrameDoctorSettings.Log($"[FrameDoctor] Opening browser for authentication...");
                Application.OpenURL(authUrl);

                var token = await WaitForAuthCallbackAsync(_cancellationTokenSource.Token);

                if (!string.IsNullOrEmpty(token))
                {
                    var expiry = DateTime.UtcNow.AddDays(7);
                    SetToken(token, expiry);
                    FrameDoctorSettings.Log("[FrameDoctor] Authentication successful");
                    return true;
                }

                Debug.LogWarning("[FrameDoctor] Authentication failed or was cancelled");
                return false;
            }
            catch (OperationCanceledException)
            {
                FrameDoctorSettings.Log("[FrameDoctor] Authentication cancelled");
                return false;
            }
            catch (Exception e)
            {
                Debug.LogError($"[FrameDoctor] Authentication error: {e.Message}");
                return false;
            }
            finally
            {
                StopListener();
            }
        }

        public void CancelLogin()
        {
            _cancellationTokenSource?.Cancel();
            StopListener();
        }

        private async Task<string> WaitForAuthCallbackAsync(CancellationToken cancellationToken)
        {
            var timeoutTask = Task.Delay(TimeSpan.FromSeconds(AuthTimeoutSeconds), cancellationToken);

            while (!cancellationToken.IsCancellationRequested)
            {
                var contextTask = _httpListener.GetContextAsync();
                var completedTask = await Task.WhenAny(contextTask, timeoutTask);

                if (completedTask == timeoutTask)
                {
                    Debug.LogWarning("[FrameDoctor] Authentication timed out");
                    return null;
                }

                var context = await contextTask;
                var request = context.Request;
                var response = context.Response;

                if (request.Url.AbsolutePath == "/callback")
                {
                    var token = request.QueryString["token"];
                    var error = request.QueryString["error"];

                    string responseHtml;
                    if (!string.IsNullOrEmpty(error))
                    {
                        responseHtml = GetErrorHtml(error);
                        SendResponse(response, responseHtml, 400);
                        return null;
                    }

                    if (!string.IsNullOrEmpty(token))
                    {
                        responseHtml = GetSuccessHtml();
                        SendResponse(response, responseHtml, 200);
                        return token;
                    }
                }

                SendResponse(response, "Invalid request", 400);
            }

            return null;
        }

        private void SendResponse(HttpListenerResponse response, string content, int statusCode)
        {
            try
            {
                response.StatusCode = statusCode;
                response.ContentType = "text/html";
                var buffer = Encoding.UTF8.GetBytes(content);
                response.ContentLength64 = buffer.Length;
                response.OutputStream.Write(buffer, 0, buffer.Length);
                response.Close();
            }
            catch (Exception e)
            {
                Debug.LogWarning($"[FrameDoctor] Error sending response: {e.Message}");
            }
        }

        private string GetSuccessHtml()
        {
            return @"<!DOCTYPE html>
<html>
<head>
    <title>FrameDoctor - Authenticated</title>
    <style>
        body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
               display: flex; justify-content: center; align-items: center; height: 100vh;
               margin: 0; background: #1a1a2e; color: #eee; }
        .container { text-align: center; }
        h1 { color: #06d6a0; }
        p { color: #888; }
    </style>
</head>
<body>
    <div class='container'>
        <h1>Authentication Successful</h1>
        <p>You can close this window and return to Unity.</p>
    </div>
</body>
</html>";
        }

        private string GetErrorHtml(string error)
        {
            return $@"<!DOCTYPE html>
<html>
<head>
    <title>FrameDoctor - Error</title>
    <style>
        body {{ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
               display: flex; justify-content: center; align-items: center; height: 100vh;
               margin: 0; background: #1a1a2e; color: #eee; }}
        .container {{ text-align: center; }}
        h1 {{ color: #ff6b6b; }}
        p {{ color: #888; }}
    </style>
</head>
<body>
    <div class='container'>
        <h1>Authentication Failed</h1>
        <p>{error}</p>
        <p>Please try again from Unity.</p>
    </div>
</body>
</html>";
        }

        private int FindAvailablePort()
        {
            for (int port = ListenerPortMin; port <= ListenerPortMax; port++)
            {
                try
                {
                    var listener = new HttpListener();
                    listener.Prefixes.Add($"http://localhost:{port}/");
                    listener.Start();
                    listener.Stop();
                    return port;
                }
                catch
                {
                    continue;
                }
            }
            return -1;
        }

        private void StopListener()
        {
            try
            {
                _httpListener?.Stop();
                _httpListener?.Close();
            }
            catch
            {
                // Ignore cleanup errors
            }
            _httpListener = null;
        }

        public void Dispose()
        {
            if (_isDisposed) return;
            _isDisposed = true;

            _cancellationTokenSource?.Cancel();
            _cancellationTokenSource?.Dispose();
            StopListener();
        }
    }
}
