using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using SVSim.BattleNode.Bridge;
using SVSim.BattleNode.Sessions;
using SVSim.Database.Enums;
using SVSim.Database.Services.Friend;
using SVSim.Database.Services.Replay;
using SVSim.EmulatedEntrypoint.Constants;
using SVSim.EmulatedEntrypoint.Extensions;
using SVSim.EmulatedEntrypoint.Matching;
using SVSim.EmulatedEntrypoint.Models.Dtos.RankBattle;
using SVSim.EmulatedEntrypoint.Models.Dtos.Requests;
using SVSim.EmulatedEntrypoint.Security.SteamSessionAuthentication;
using SVSim.EmulatedEntrypoint.Services;

namespace SVSim.EmulatedEntrypoint.Controllers;

/// <summary>
/// Rank battle family — covers rotation/unlimited human PvP + AI variants. Crossover
/// is out of scope (no AI variant; human-only). Multi-prefix URLs (rotation_rank_battle/,
/// unlimited_rank_battle/, ai_*_rank_battle/, rank_battle/) require explicit absolute
/// route attributes on each action; the controller doesn't extend SVSimController's
/// [Route("[controller]")] convention.
/// </summary>
[ApiController]
[Authorize(AuthenticationSchemes = SteamAuthenticationConstants.SchemeName)]
public sealed class RankBattleController : ControllerBase
{
    private readonly IMatchingResolver _resolver;
    private readonly IBattleSessionStore _sessionStore;
    private readonly IMatchContextBuilder _ctxBuilder;
    private readonly IBotRoster _botRoster;
    private readonly IBattleContextStore _battleContextStore;
    private readonly IBattleHistoryWriter _historyWriter;
    private readonly IPlayedTogetherWriter _playedTogetherWriter;
    private readonly ILogger<RankBattleController> _log;

    public RankBattleController(
        IMatchingResolver resolver,
        IBattleSessionStore sessionStore,
        IMatchContextBuilder ctxBuilder,
        IBotRoster botRoster,
        IBattleContextStore battleContextStore,
        IBattleHistoryWriter historyWriter,
        IPlayedTogetherWriter playedTogetherWriter,
        ILogger<RankBattleController> log)
    {
        _resolver = resolver;
        _sessionStore = sessionStore;
        _ctxBuilder = ctxBuilder;
        _botRoster = botRoster;
        _battleContextStore = battleContextStore;
        _historyWriter = historyWriter;
        _playedTogetherWriter = playedTogetherWriter;
        _log = log;
    }

    private bool TryGetViewerId(out long viewerId)
    {
        viewerId = 0;
        var claim = User.Claims.FirstOrDefault(c => c.Type == ShadowverseClaimTypes.ViewerIdClaim)?.Value;
        return claim is not null && long.TryParse(claim, out viewerId);
    }

    [HttpPost("/rotation_rank_battle/do_matching")]
    public Task<IActionResult> DoMatchingRotation([FromBody] DoMatchingRequestDto req, CancellationToken ct)
        => DoMatchingInternal("rotation_rank_battle", Format.Rotation, req, ct);

    [HttpPost("/unlimited_rank_battle/do_matching")]
    public Task<IActionResult> DoMatchingUnlimited([FromBody] DoMatchingRequestDto req, CancellationToken ct)
        => DoMatchingInternal("unlimited_rank_battle", Format.Unlimited, req, ct);

    // AIBattleStartTask has no SetParameter override, so the body is just the inherited
    // PostParams (viewer_id / steam_id / steam_session_ticket) — but the translation
    // middleware requires at least one parameter to bind the decrypted body. Use BaseRequest.
    [HttpPost("/ai_rotation_rank_battle/start")]
    public Task<IActionResult> AiStartRotation([FromBody] BaseRequest _, CancellationToken ct)
        => AiStartInternal(Format.Rotation, ct);

    [HttpPost("/ai_unlimited_rank_battle/start")]
    public Task<IActionResult> AiStartUnlimited([FromBody] BaseRequest _, CancellationToken ct)
        => AiStartInternal(Format.Unlimited, ct);

    /// <summary>
    /// Shared finish handler — RankBattleFinishTask parses the same wire shape for
    /// all four URLs and routes server-side by URL (vs IsAINetwork flag in the client).
    /// Stubbed for Phase 3: echo battle_result, emit zeros elsewhere. Real rank
    /// progression math is a separate spec.
    /// </summary>
    [HttpPost("/rotation_rank_battle/finish")]
    [HttpPost("/unlimited_rank_battle/finish")]
    [HttpPost("/ai_rotation_rank_battle/finish")]
    [HttpPost("/ai_unlimited_rank_battle/finish")]
    public async Task<IActionResult> Finish([FromBody] RankBattleFinishRequestDto req, CancellationToken ct)
    {
        if (!TryGetViewerId(out var vid)) return Unauthorized();

        var ctx = _battleContextStore.TakeFor(vid);
        bool isWin = req.BattleResult == 1;

        await _historyWriter.RecordAsync(vid, ctx, isWin, ct);

        // Played-together only fires for human PvP. AI bots have OpponentViewerId=0.
        if (ctx is { OpponentViewerId: > 0 })
        {
            await _playedTogetherWriter.RecordAsync(
                vid,
                ctx.OpponentViewerId,
                new BattleParticipationContext(
                    PlayedMode: 0,
                    BattleType: ctx.BattleType,
                    DeckFormat: ctx.DeckFormat,
                    TwoPickType: ctx.TwoPickType),
                ct);
        }

        return Ok(new RankBattleFinishResponseDto
        {
            BattleResult = req.BattleResult,
            // All other fields default to 0 in the DTO (ClassLevel defaults to 1).
        });
    }

    // BaseRequest parameter on every body-less action so the translation middleware can
    // bind the decrypted msgpack body (it explicitly requires at least one parameter).
    [HttpPost("/rank_battle/force_finish")]
    public IActionResult ForceFinish([FromBody] BaseRequest _)
    {
        if (!TryGetViewerId(out var _u)) return Unauthorized();
        return Ok(new { });
    }

    [HttpPost("/rank_battle/add_client_log")]
    [HttpPost("/rank_battle/add_all_client_log")]
    [HttpPost("/rank_battle/add_last_turn_log")]
    public IActionResult AddClientLog([FromBody] BaseRequest _)
    {
        if (!TryGetViewerId(out var _u)) return Unauthorized();
        return Ok(new { });
    }

    [HttpPost("/rank_battle/get_latest_master_point")]
    public IActionResult GetLatestMasterPoint([FromBody] BaseRequest _)
    {
        if (!TryGetViewerId(out var _u)) return Unauthorized();
        return Ok(new { });
    }

    private async Task<IActionResult> DoMatchingInternal(string mode, Format format, DoMatchingRequestDto req, CancellationToken ct)
    {
        if (!TryGetViewerId(out var vid)) return Unauthorized();

        MatchContext ctx;
        try
        {
            ctx = await _ctxBuilder.BuildForRankBattleAsync(vid, format, req.DeckNo);
        }
        catch (InvalidOperationException ex)
        {
            // Most likely cause: viewer has no deck at that slot for this format. Surface
            // as 3001 RC_BATTLE_MATCHING_ILLEGAL — the client shows the standard
            // matchmaking-error dialog rather than retrying forever.
            _log.LogWarning(ex, "BuildForRankBattleAsync failed for viewer {Vid} format {Fmt} deckNo {DeckNo}; returning 3001.", vid, format, req.DeckNo);
            return Ok(new DoMatchingResponseDto { MatchingState = 3001, NodeServerUrl = "" });
        }

        var r = await _resolver.ResolveAsync(mode, new BattlePlayer(vid, ctx), ct);

        return Ok(new DoMatchingResponseDto
        {
            MatchingState = r.MatchingState,
            BattleId = r.BattleId,
            NodeServerUrl = r.NodeServerUrl,
            // Placeholder per spec § Out of scope — per-battle card-master split is deferred.
            CardMasterId = 0,
        });
    }

    private async Task<IActionResult> AiStartInternal(Format format, CancellationToken ct)
    {
        if (!TryGetViewerId(out var vid)) return Unauthorized();

        // The /ai_<fmt>/start request body is BaseRequest only — it carries no deck_no.
        // The deck the viewer queued with was captured in the PendingBattle's MatchContext
        // at /do_matching resolution time (when InProcessPairUp called bridge.RegisterBattle).
        // Reuse that context so SelfInfo's classId/charaId/sleeveId match what the user
        // actually picked. Rebuilding from deck #1 was the 2026-06-02 wire-bug — surfaced
        // as "queued Bloodcraft, saw Swordcraft leader."
        var pending = _sessionStore.TryFindPendingForViewer(vid);
        if (pending is null)
        {
            _log.LogWarning("AiStart for viewer {Vid} format {Fmt} has no pending battle; returning ai_id=-1.", vid, format);
            return Ok(new AiBattleStartResponseDto { AiId = -1 });
        }
        var selfCtx = pending.P1.Context;

        var bot = await _botRoster.PickAsync(selfCtx, pending.BattleId, ct);
        var seed = Random.Shared.Next();

        // Stash battle context for the upcoming /finish so the replay-history hook can
        // compose a ViewerBattleHistory row. See docs/superpowers/specs/2026-06-10-replay-info-design.md.
        if (long.TryParse(pending.BattleId, out var battleIdLong))
        {
            _battleContextStore.Set(vid, new BattleContext(
                BattleId:           battleIdLong,
                // Wire battle_type: 2 = rank battle (per docs/api-spec/common/types.ts.md
                // #battle-types). AI variant shares the rank-battle wire id.
                BattleType:         2,
                DeckFormat:         format.ToApi(),                                // wire-int via existing converter
                TwoPickType:        0,
                SelfClassId:        (int)selfCtx.ClassId,                          // CardClass enum
                SelfSubClassId:     0,
                SelfCharaId:        int.TryParse(selfCtx.CharaId, out var ch) ? ch : 0,  // CharaId is string on MatchContext
                SelfRotationId:     "0",
                OpponentViewerId:   0,                                             // AI bot — not a real viewer
                OpponentName:       bot.UserName,
                OpponentClassId:    bot.ClassId,                                   // int on AIBotProfile
                OpponentSubClassId: 0,
                OpponentCharaId:    bot.CharaId,                                   // int on AIBotProfile
                OpponentCountryCode: bot.CountryCode,
                OpponentEmblemId:   bot.EmblemId,                                  // int → long widen
                OpponentDegreeId:   bot.DegreeId,                                  // int → long widen
                OpponentRotationId: "0",
                BattleStartTime:    DateTime.UtcNow));
        }

        // Per spec, ai-start.md TODO: turnState semantics unclear. Default 0 (player first).
        return Ok(new AiBattleStartResponseDto
        {
            AiId = bot.AiId,
            TurnState = 0,
            SelfInfo = new AiBattlePlayerInfo
            {
                CountryCode = selfCtx.CountryCode,
                UserName = selfCtx.UserName,
                SleeveId = int.TryParse(selfCtx.SleeveId, out var sId) ? sId : -1,
                EmblemId = int.TryParse(selfCtx.EmblemId, out var eId) ? eId : -1,
                DegreeId = int.TryParse(selfCtx.DegreeId, out var dId) ? dId : -1,
                FieldId = selfCtx.FieldId,
                IsOfficial = selfCtx.IsOfficial,
                OppoId = bot.AiId,
                Seed = seed,
                Rank = 0,
                BattlePoint = 0,
                ClassId = (int)selfCtx.ClassId,
                CharaId = int.TryParse(selfCtx.CharaId, out var chId) ? chId : -1,
                IsMasterRank = 0,
                MasterPoint = 0,
            },
            OppoInfo = new AiBattlePlayerInfo
            {
                CountryCode = bot.CountryCode,
                UserName = bot.UserName,
                SleeveId = bot.SleeveId,
                EmblemId = bot.EmblemId,
                DegreeId = bot.DegreeId,
                FieldId = bot.FieldId,
                IsOfficial = bot.IsOfficial,
                OppoId = (int)vid,
                Seed = seed,
                Rank = bot.Rank,
                BattlePoint = bot.BattlePoint,
                ClassId = bot.ClassId,
                CharaId = bot.CharaId,
                IsMasterRank = bot.IsMasterRank,
                MasterPoint = bot.MasterPoint,
            },
        });
    }
}
