using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.RateLimiting;
using Microsoft.Extensions.Options;
using Tiku.Application.Auth;
using Tiku.Api.Contracts;
using Tiku.Api.Options;
using Tiku.Application.Security;
using Tiku.Application.Tenancy;
using Tiku.Infrastructure.Content;
using Tiku.Domain.Tenancy;

namespace Tiku.Api.Controllers;

[ApiController]
[Tags("租户端-认证")]
[Route("api/auth")]
[Produces("application/json")]
public sealed class AuthController(
    IAuthService authService,
    ISmsVerificationService smsVerificationService,
    IAuthSessionStore sessionStore,
    ITenantContext tenantContext,
    ITenantContextInitializer tenantContextInitializer,
    ITenantDirectory tenantDirectory,
    ICurrentUser currentUser,
    IOptions<TenantResolutionOptions> tenantResolutionOptions) : ControllerBase
{
    [AllowAnonymous]
    [EnableRateLimiting(AuthRateLimitPolicies.Sms)]
    [HttpPost("sms/send")]
    [EndpointSummary("发送短信验证码")]
    [EndpointDescription("发送登录用途短信验证码，并应用租户级短信限流。")]
    [ProducesResponseType<SmsSendResult>(StatusCodes.Status202Accepted)]
    [ProducesResponseType<ProblemDetails>(StatusCodes.Status400BadRequest)]
    [ProducesResponseType<ProblemDetails>(StatusCodes.Status429TooManyRequests)]
    public async Task<ActionResult<SmsSendResult>> SendSmsCode(
        [FromBody] SendSmsCodeDto request,
        CancellationToken cancellationToken)
    {
        var realm = request.Realm!.Value;
        if (realm != AuthRealm.Tenant)
        {
            throw new RequiredFieldException("SMS authentication is only available in the tenant realm.");
        }

        var tenantId = await ResolveRealmTenantIdAsync(realm, request.TenantCode, cancellationToken)
            ?? throw new RequiredFieldException("tenantCode is required for SMS authentication.");
        var result = await smsVerificationService.CreateCodeAsync(
            new SendSmsCodeRequest(
                tenantId,
                request.Phone,
                SmsPurpose.Login,
                GetIpAddress(),
                Request.Headers.UserAgent.ToString(),
                request.DeviceId),
            cancellationToken);
        return Accepted(result);
    }

    [AllowAnonymous]
    [EnableRateLimiting(AuthRateLimitPolicies.Password)]
    [HttpPost("login/password")]
    [EndpointSummary("手机号密码登录")]
    [EndpointDescription("使用本地手机号和密码登录，签发 JWT access token 与数据库 refresh/session。")]
    [ProducesResponseType<AuthenticationResultDto>(StatusCodes.Status200OK)]
    [ProducesResponseType<ProblemDetails>(StatusCodes.Status401Unauthorized)]
    public async Task<ActionResult<AuthenticationResultDto>> LoginWithPassword(
        [FromBody] PasswordLoginDto request,
        CancellationToken cancellationToken)
    {
        var realm = request.Realm!.Value;
        var identifier = request.Identifier ?? request.Phone;
        if (string.IsNullOrWhiteSpace(identifier))
        {
            throw new RequiredFieldException("identifier is required.");
        }
        var result = await authService.LoginWithPasswordAsync(
            new PasswordLoginRequest(
                realm,
                await ResolveRealmTenantIdAsync(realm, request.TenantCode, cancellationToken),
                identifier,
                request.Password,
                GetIpAddress(),
                Request.Headers.UserAgent.ToString()),
            cancellationToken);

        return Ok(AuthenticationResultDto.FromApplication(result));
    }

    [AllowAnonymous]
    [EnableRateLimiting(AuthRateLimitPolicies.Sms)]
    [HttpPost("login/sms")]
    [EndpointSummary("短信验证码登录")]
    [EndpointDescription("校验已发送的登录用途短信验证码，成功后签发 JWT access token 与数据库 refresh/session。")]
    [ProducesResponseType<AuthenticationResultDto>(StatusCodes.Status200OK)]
    [ProducesResponseType<ProblemDetails>(StatusCodes.Status401Unauthorized)]
    public async Task<ActionResult<AuthenticationResultDto>> LoginWithSms(
        [FromBody] SmsLoginDto request,
        CancellationToken cancellationToken)
    {
        var realm = request.Realm!.Value;
        var result = await authService.LoginWithSmsAsync(
            new SmsLoginRequest(
                realm,
                await ResolveRealmTenantIdAsync(realm, request.TenantCode, cancellationToken),
                request.Phone,
                request.Code,
                GetIpAddress(),
                Request.Headers.UserAgent.ToString()),
            cancellationToken);

        return Ok(AuthenticationResultDto.FromApplication(result));
    }

    [AllowAnonymous]
    [HttpPost("oauth/wechat")]
    [EndpointSummary("微信网页 OAuth 登录")]
    [EndpointDescription("使用微信网页授权 code 换取 openid/unionid，upsert 用户身份并创建应用会话。")]
    [ProducesResponseType<AuthenticationResultDto>(StatusCodes.Status200OK)]
    [ProducesResponseType<ProblemDetails>(StatusCodes.Status401Unauthorized)]
    [ProducesResponseType<ProblemDetails>(StatusCodes.Status503ServiceUnavailable)]
    public async Task<ActionResult<AuthenticationResultDto>> LoginWithWechatWeb(
        [FromBody] OAuthCodeDto request,
        CancellationToken cancellationToken)
    {
        var realm = request.Realm!.Value;
        var result = await authService.LoginWithWechatWebAsync(
            new WechatLoginRequest(
                realm,
                await ResolveRealmTenantIdAsync(realm, request.TenantCode, cancellationToken),
                request.Code,
                GetIpAddress(),
                Request.Headers.UserAgent.ToString()),
            cancellationToken);

        return Ok(AuthenticationResultDto.FromApplication(result));
    }

    [AllowAnonymous]
    [HttpPost("oauth/wechat-miniapp")]
    [EndpointSummary("微信小程序登录")]
    [EndpointDescription("使用小程序 wx.login 返回的 code 换取 openid/session_key，upsert 用户身份并创建应用会话。")]
    [ProducesResponseType<AuthenticationResultDto>(StatusCodes.Status200OK)]
    [ProducesResponseType<ProblemDetails>(StatusCodes.Status401Unauthorized)]
    [ProducesResponseType<ProblemDetails>(StatusCodes.Status503ServiceUnavailable)]
    public async Task<ActionResult<AuthenticationResultDto>> LoginWithWechatMiniApp(
        [FromBody] OAuthCodeDto request,
        CancellationToken cancellationToken)
    {
        var realm = request.Realm!.Value;
        var result = await authService.LoginWithWechatMiniAppAsync(
            new WechatLoginRequest(
                realm,
                await ResolveRealmTenantIdAsync(realm, request.TenantCode, cancellationToken),
                request.Code,
                GetIpAddress(),
                Request.Headers.UserAgent.ToString()),
            cancellationToken);

        return Ok(AuthenticationResultDto.FromApplication(result));
    }

    [AllowAnonymous]
    [HttpPost("refresh")]
    [EndpointSummary("刷新登录会话")]
    [EndpointDescription("使用 refresh token 轮换数据库 session，并签发新的 access/refresh token。")]
    public async Task<ActionResult<AuthTokenPair>> Refresh(
        [FromBody] RefreshSessionDto request,
        CancellationToken cancellationToken)
    {
        ResolveRefreshTokenTenant(request.RefreshToken);
        var result = await authService.RefreshAsync(
            new RefreshSessionRequest(
                request.RefreshToken,
                GetIpAddress(),
                Request.Headers.UserAgent.ToString()),
            cancellationToken);

        return Ok(result);
    }

    [AllowAnonymous]
    [HttpPost("logout")]
    [EndpointSummary("退出登录")]
    [EndpointDescription("撤销 refresh token 对应的数据库 session；session 校验开启时，旧 access token 也会被拒绝。")]
    [ProducesResponseType(StatusCodes.Status204NoContent)]
    public async Task<IActionResult> Logout(
        [FromBody] RefreshSessionDto request,
        CancellationToken cancellationToken)
    {
        ResolveRefreshTokenTenant(request.RefreshToken);
        await authService.LogoutAsync(
            new LogoutSessionRequest(request.RefreshToken),
            cancellationToken);

        return NoContent();
    }

    [HttpPost("logout-all")]
    [Authorize]
    [EndpointSummary("退出全部登录会话")]
    [EndpointDescription("撤销当前用户全部 refresh/session，会话校验开启时旧 access token 也会被拒绝。")]
    [ProducesResponseType(StatusCodes.Status204NoContent)]
    public async Task<IActionResult> LogoutAll(CancellationToken cancellationToken)
    {
        if (currentUser.UserId is not { } userId)
        {
            return Unauthorized();
        }

        await authService.LogoutAllAsync(userId, cancellationToken);
        return NoContent();
    }

    [AllowAnonymous]
    [HttpPost("password/change-required")]
    [EnableRateLimiting(AuthRateLimitPolicies.Password)]
    [EndpointSummary("修改首次登录必改密码")]
    [EndpointDescription("校验密码变更挑战令牌并设置新密码，成功后签发新的登录会话。")]
    public async Task<ActionResult<AuthenticationResultDto>> ChangeRequiredPassword(
        [FromBody] RequiredPasswordChangeDto request,
        CancellationToken cancellationToken)
    {
        ResolveAuthChallengeTenant(request.ChallengeToken);
        var result = await authService.ChangeRequiredPasswordAsync(
            new PasswordChangeChallengeRequest(
                request.ChallengeToken, request.NewPassword, GetIpAddress(), Request.Headers.UserAgent.ToString()),
            cancellationToken);
        return Ok(AuthenticationResultDto.FromApplication(result));
    }

    private void ResolveRefreshTokenTenant(string refreshToken)
    {
        if (!sessionStore.TryParseRefreshToken(refreshToken, out var locator))
        {
            return;
        }

        if (locator.Realm == AuthRealm.Platform)
        {
            EnsurePlatformHost();
            if (tenantContext.IsResolved)
            {
                throw new TenantContextConflictException(tenantContext.TenantId!.Value, Guid.Empty);
            }
            return;
        }

        if (!tenantContext.IsResolved)
        {
            throw new RequiredFieldException(
                "tenant refresh/logout requires a tenant host or x-tenant-code matching the refresh token.");
        }

        tenantContextInitializer.Initialize(locator.TenantId!.Value, null, TenantResolutionSource.RefreshToken);
    }

    private void ResolveAuthChallengeTenant(string challengeToken)
    {
        var parts = challengeToken.Split('.', 4, StringSplitOptions.None);
        if (parts.Length != 4 || parts[0] != "c1")
        {
            return;
        }

        if (parts[1] == "p" && parts[2] == "-")
        {
            EnsurePlatformHost();
            if (tenantContext.IsResolved)
            {
                throw new TenantContextConflictException(tenantContext.TenantId!.Value, Guid.Empty);
            }
            return;
        }

        if (parts[1] != "t" || !Guid.TryParseExact(parts[2], "N", out var tenantId) || !tenantContext.IsResolved)
        {
            throw new RequiredFieldException(
                "tenant authentication challenge requires a tenant host or x-tenant-code.");
        }

        tenantContextInitializer.Initialize(tenantId, null, TenantResolutionSource.RefreshToken);
    }

    private string? GetIpAddress()
    {
        return HttpContext.Connection.RemoteIpAddress?.ToString();
    }

    private async Task<Guid?> ResolveRealmTenantIdAsync(
        AuthRealm realm,
        string? tenantCode,
        CancellationToken cancellationToken)
    {
        if (realm == AuthRealm.Platform)
        {
            EnsurePlatformHost();
            if (tenantContext.IsResolved || !string.IsNullOrWhiteSpace(tenantCode))
            {
                throw new RequiredFieldException("platform realm does not accept tenantCode and must use a platform host.");
            }

            return null;
        }

        if (tenantContext.TenantId.HasValue)
        {
            if (!string.IsNullOrWhiteSpace(tenantCode) &&
                !string.Equals(tenantContext.TenantCode, tenantCode.Trim(), StringComparison.OrdinalIgnoreCase))
            {
                var supplied = await tenantDirectory.FindByCodeAsync(tenantCode.Trim(), cancellationToken);
                if (supplied?.TenantId != tenantContext.TenantId.Value)
                {
                    throw new TenantContextConflictException(
                        tenantContext.TenantId.Value,
                        supplied?.TenantId ?? Guid.Empty);
                }
            }

            return tenantContext.TenantId.Value;
        }

        if (string.IsNullOrWhiteSpace(tenantCode))
        {
            throw new RequiredFieldException("tenantCode is required when the request host does not resolve a tenant.");
        }

        var tenant = await tenantDirectory.FindByCodeAsync(tenantCode.Trim(), cancellationToken)
            ?? throw new TenantNotFoundException();
        tenantContextInitializer.Initialize(
            tenant.TenantId,
            tenant.TenantCode,
            TenantResolutionSource.TenantCode);
        return tenant.TenantId;
    }

    private void EnsurePlatformHost()
    {
        var requestHost = Request.Host.Host.Trim().TrimEnd('.');
        if (!tenantResolutionOptions.Value.PlatformHosts.Any(host =>
                string.Equals(
                    host.Trim().TrimEnd('.'),
                    requestHost,
                    StringComparison.OrdinalIgnoreCase)))
        {
            throw new RequiredFieldException("platform realm is only available on a configured platform host.");
        }
    }
}
