using Application.Abstractions.Cache;
using Application.Abstractions.Chat;
using Application.Common;
using SharedKernel.Helpers;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using StackExchange.Redis;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

namespace Admin.Pages.Member;

public class VisitorModel(
    IChatConnectionTracker tracker,
    IConnectionMultiplexer redis
) : PageModel {

    [BindProperty(SupportsGet = true)]
    public QueryParams Query { get; set; } = new();

    public sealed class QueryParams
    {
        [Range(1, int.MaxValue)]
        [DisplayName("페이지 번호")]
        public int PageNum { get; set; } = 1;

        [Range(1, 100)]
        [DisplayName("페이지 목록 수")]
        public ushort PerPage { get; set; } = 20;

        [Range(1, 2, ErrorMessage = "{0}이(가) 올바르지 않습니다.")]
        [DisplayName("검색 조건")]
        public int? Search { get; set; }

        [MaxLength(100, ErrorMessage = "{0}은(는) {1}자 이하로 입력하세요.")]
        [DisplayName("검색어")]
        public string? Keyword { get; set; }

        [DisplayName("검색 구분")]
        public int? Tab { get; set; }
    }

    public int TotalRows { get; set; }
    public int TotalMember { get; set; }
    public int TotalGuest { get; set; }
    public int TotalIps { get; set; }

    public List<(
        int Num,
        string ConnectionID,
        bool IsGuest,
        string? MemberID,
        string? Email,
        string IpAddress,
        string Browser,
        string OS,
        string Device,
        string ConnectedAt,
        string LogoutURL,
        int Connections
    )> List { get; set; } = [];

    public Pagination? Pagination { get; set; }

    public async Task OnGetAsync(CancellationToken _)
    {
        if (!ModelState.IsValid)
        {
            return;
        }

        var all = await tracker.GetAllAsync();

        TotalRows = all.Count;
        TotalMember = all.Count(x => !x.IsGuest);
        TotalGuest = all.Count(x => x.IsGuest);
        TotalIps = all.Select(x => x.IpAddress).Distinct().Count();

        if (Query.Tab == 3)
        {
            // IP 목록 탭
            var ipGroups = all
                .GroupBy(x => x.IpAddress)
                .Select(g => new
                {
                    IpAddress = g.Key,
                    ConnectedAt = g.Min(x => x.ConnectedAt),
                    SampleUA = g.First().UserAgent,
                    Connections = g.Count()
                })
                .OrderByDescending(x => x.Connections)
                .ToList();

            var skip = (Query.PageNum - 1) * Query.PerPage;
            var paged = ipGroups.Skip(skip).Take(Query.PerPage).ToList();
            int num = skip + 1;

            List = [..paged.Select(c => (
                Num: num++,
                ConnectionID: "",
                IsGuest: false,
                MemberID: (string?)null,
                Email: (string?)null,
                c.IpAddress,
                Browser: ParseUA(c.SampleUA, "browser"),
                OS: ParseUA(c.SampleUA, "os"),
                Device: ParseUA(c.SampleUA, "device"),
                ConnectedAt: c.ConnectedAt.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss"),
                LogoutURL: "",
                c.Connections
            ))];

            Pagination = new Pagination(ipGroups.Count, Query.PageNum, Query.PerPage);
        }
        else
        {
            // 검색 필터
            IEnumerable<ConnectedUser> filtered = all;
            if (!string.IsNullOrWhiteSpace(Query.Keyword))
            {
                filtered = Query.Search == 1
                    ? filtered.Where(x => x.MemberID.HasValue && x.MemberID.Value.ToString() == Query.Keyword)
                    : filtered.Where(x => x.Email != null && x.Email == Query.Keyword);
            }

            // 탭 필터
            filtered = Query.Tab switch
            {
                1 => filtered.Where(x => !x.IsGuest),
                2 => filtered.Where(x => x.IsGuest),
                _ => filtered
            };

            var sorted = filtered.OrderByDescending(x => x.ConnectedAt).ToList();
            var skip = (Query.PageNum - 1) * Query.PerPage;
            var paged = sorted.Skip(skip).Take(Query.PerPage).ToList();
            int num = skip + 1;

            var qs = Request.QueryString.ToString();

            List = [..paged.Select(c => (
                Num: num++,
                ConnectionID: c.ConnectionId,
                c.IsGuest,
                MemberID: c.MemberID?.ToString(),
                c.Email,
                c.IpAddress,
                Browser: ParseUA(c.UserAgent, "browser"),
                OS: ParseUA(c.UserAgent, "os"),
                Device: ParseUA(c.UserAgent, "device"),
                ConnectedAt: c.ConnectedAt.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss"),
                LogoutURL: $"/Member/Visitor?handler=Kick&connectionId={c.ConnectionId}",
                Connections: 0
            ))];

            Pagination = new Pagination(sorted.Count, Query.PageNum, Query.PerPage);
        }
    }

    public async Task<IActionResult> OnGetKickAsync(string connectionId, CancellationToken ct)
    {
        try
        {
            if (string.IsNullOrEmpty(connectionId))
            {
                throw new Exception("강제 종료할 대상이 없습니다.");
            }

            await PublishKick(connectionId);

            TempData["SuccessMessage"] = "강제 종료되었습니다.";
        }
        catch (Exception e)
        {
            TempData["ErrorMessages"] = e.Message;
        }

        return RedirectToPage(Query);
    }

    public async Task<IActionResult> OnPostKickAsync(string[] ids, CancellationToken ct)
    {
        try
        {
            if (ids.Length == 0)
            {
                throw new Exception("강제 종료할 항목을 선택해주세요.");
            }

            foreach (var id in ids)
            {
                await PublishKick(id);
            }

            TempData["SuccessMessage"] = $"{ids.Length}건이 강제 종료되었습니다.";
        }
        catch (Exception e)
        {
            TempData["ErrorMessages"] = e.Message;
        }

        return RedirectToPage(Query);
    }

    // Redis Pub/Sub을 통해 해당 ConnectionID를 구독 중인 클라이언트에게 강제 종료 메시지를 보냄
    private async Task PublishKick(string connectionId)
    {
        await redis.GetSubscriber().PublishAsync(
            RedisChannel.Literal(CacheKeys.ChatKickChannel), connectionId
        );
    }

    // User-Agent 문자열에서 브라우저, OS, 디바이스 정보를 추출하는 헬퍼 메서드
    private static string ParseUA(string? ua, string type)
    {
        if (string.IsNullOrWhiteSpace(ua))
        {
            return "-";
        }

        return type switch
        {
            "browser" => UserAgentParser.ExtractBrowser(ua),
            "os" => UserAgentParser.ExtractOS(ua),
            "device" => UserAgentParser.ExtractDevice(ua),
            _ => "-"
        };
    }
}