using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System.Text.Json;
using Tiku.Api.Contracts;
using Tiku.Domain.Common;
using Tiku.Domain.Operations;
using Tiku.Domain.Tenancy;
using Tiku.Infrastructure.Persistence;
using Tiku.Application.Security;
using Tiku.Application.Tenancy;

namespace Tiku.Api.Controllers;

[ApiController]
[Tags("租户端-公开配置")]
[AllowAnonymous]
[Produces("application/json")]
[Route("api/tenant")]
public sealed class TenantPublicController(
    TikuDbContext dbContext,
    ITenantContext tenantContext,
    ITenantContextInitializer tenantContextInitializer,
    ITenantDirectory tenantDirectory) : ControllerBase
{
    [HttpGet("resolve")]
    [EndpointSummary("解析当前租户")]
    [EndpointDescription("根据访问域名、host 参数、x-tenant-code 或 tenantCode 查询启用中的租户及公开品牌配置。")]
    [ProducesResponseType<TenantResolveResponseDto>(StatusCodes.Status200OK)]
    [ProducesResponseType<ProblemDetails>(StatusCodes.Status404NotFound)]
    public async Task<ActionResult<TenantResolveResponseDto>> Resolve(
        [FromQuery] TenantResolveQueryDto query,
        CancellationToken cancellationToken)
    {
        var tenantCode = NormalizeTenantCode(query.TenantCode ?? Request.Headers["x-tenant-code"].FirstOrDefault());
        var host = NormalizeHost(query.Host ?? Request.Host.Host);

        var directoryEntry = tenantContext.ResolutionSource == TenantResolutionSource.Host
            ? await tenantDirectory.FindByHostAsync(Request.Host.Host, cancellationToken)
            : !string.IsNullOrWhiteSpace(tenantCode)
                ? await tenantDirectory.FindByCodeAsync(tenantCode, cancellationToken)
                : string.IsNullOrWhiteSpace(host)
                    ? null
                    : await tenantDirectory.FindByHostAsync(host, cancellationToken);

        TenantLookupResult? tenant = directoryEntry is null
            ? null
            : new TenantLookupResult(
                directoryEntry.TenantId,
                directoryEntry.TenantCode,
                directoryEntry.Name,
                directoryEntry.Status,
                directoryEntry.Mode,
                directoryEntry.Host);

        if (tenant is null)
        {
            return NotFound(new ProblemDetails
            {
                Title = "Tenant was not found.",
                Status = StatusCodes.Status404NotFound,
                Detail = "No active tenant matches the supplied host or tenantCode."
            });
        }

        tenantContextInitializer.Initialize(
            tenant.Id,
            tenant.Slug,
            tenant.Host is null ? TenantResolutionSource.TenantCode : TenantResolutionSource.Host);

        return Ok(await BuildResponseAsync(tenant, tenant.Host, cancellationToken));
    }

    [HttpGet("current-public")]
    [EndpointSummary("获取公开租户配置")]
    [EndpointDescription("公开页面使用的租户品牌、功能开关和 public config。和 resolve 返回结构一致。")]
    [ProducesResponseType<TenantResolveResponseDto>(StatusCodes.Status200OK)]
    [ProducesResponseType<ProblemDetails>(StatusCodes.Status404NotFound)]
    public Task<ActionResult<TenantResolveResponseDto>> CurrentPublic(
        [FromQuery] TenantResolveQueryDto query,
        CancellationToken cancellationToken)
    {
        return Resolve(query, cancellationToken);
    }

    private async Task<TenantLookupResult?> FindActiveTenantByCodeAsync(
        string tenantCode,
        CancellationToken cancellationToken)
    {
        return await dbContext.Tenants
            .Where(tenant =>
                tenant.Slug == tenantCode &&
                tenant.Status == TenantStatus.Active)
            .Select(tenant => new TenantLookupResult(
                tenant.Id,
                tenant.Slug,
                tenant.Name,
                tenant.Status,
                tenant.Mode,
                null))
            .SingleOrDefaultAsync(cancellationToken);
    }

    private async Task<TenantLookupResult?> FindActiveTenantByHostAsync(
        string? host,
        CancellationToken cancellationToken)
    {
        if (string.IsNullOrWhiteSpace(host))
        {
            return null;
        }

        return await dbContext.TenantDomains
            .Where(domain =>
                domain.Host == host &&
                domain.Status == TenantDomainStatus.Active)
            .Join(
                dbContext.Tenants.Where(tenant => tenant.Status == TenantStatus.Active),
                domain => domain.TenantId,
                tenant => tenant.Id,
                (domain, tenant) => new TenantLookupResult(
                    tenant.Id,
                    tenant.Slug,
                    tenant.Name,
                    tenant.Status,
                    tenant.Mode,
                    domain.Host))
            .SingleOrDefaultAsync(cancellationToken);
    }

    private async Task<TenantResolveResponseDto> BuildResponseAsync(
        TenantLookupResult tenant,
        string? host,
        CancellationToken cancellationToken)
    {
        var branding = await dbContext.TenantBrandings.FindAsync([tenant.Id], cancellationToken);
        var settings = await dbContext.TenantSettings.FindAsync([tenant.Id], cancellationToken);
        var themeConfig = await dbContext.TenantThemeConfigs
            .SingleOrDefaultAsync(
                entity => entity.TenantId == tenant.Id &&
                          entity.Status == TenantThemeConfigStatus.Published,
                cancellationToken);

        return new TenantResolveResponseDto(
            new PublicTenantDto(
                tenant.Id,
                tenant.Slug,
                tenant.Name,
                tenant.Status,
                tenant.Mode,
                host),
            BuildBranding(branding, themeConfig),
            CloneOrDefault(settings?.FeatureFlags),
            CloneOrDefault(settings?.AdminFeatureFlags),
            CloneOrDefault(settings?.PublicConfig));
    }

    private static PublicTenantBrandingDto BuildBranding(
        TenantBranding? branding,
        TenantThemeConfig? themeConfig)
    {
        if (branding is null && themeConfig is null)
        {
            return PublicTenantBrandingDto.Empty;
        }

        return new PublicTenantBrandingDto(
            branding?.BrandName,
            branding?.ShortName,
            branding?.Slogan,
            branding?.LogoUrl,
            branding?.FaviconUrl,
            branding?.ServiceWechat,
            branding?.ServiceAccountName,
            IsNonEmptyObject(themeConfig?.ActiveTheme) ? themeConfig!.ActiveTheme.Clone() : CloneOrDefault(branding?.Theme),
            IsNonEmptyObject(themeConfig?.ActivePublicAssets) ? themeConfig!.ActivePublicAssets.Clone() : CloneOrDefault(branding?.PublicAssets));
    }

    private static string? NormalizeTenantCode(string? value)
    {
        return string.IsNullOrWhiteSpace(value) ? null : value.Trim();
    }

    private static string? NormalizeHost(string? value)
    {
        if (string.IsNullOrWhiteSpace(value))
        {
            return null;
        }

        var host = value.Trim().ToLowerInvariant();
        return host.Split(':', 2)[0];
    }

    private static bool IsNonEmptyObject(JsonElement? value)
    {
        return value is { ValueKind: JsonValueKind.Object } json &&
               json.EnumerateObject().Any();
    }

    private static JsonElement CloneOrDefault(JsonElement? value)
    {
        return value.HasValue ? value.Value.Clone() : JsonDefaults.Object();
    }

    private sealed record TenantLookupResult(
        Guid Id,
        string Slug,
        string Name,
        TenantStatus Status,
        TenantMode Mode,
        string? Host);
}
