using System.Net;
using System.Net.Mail;
using System.Net.Mime;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json.Serialization;
using System.Threading.RateLimiting;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.AspNetCore.RateLimiting;

// Stateless contact relay (TECH_SPEC §7): validate -> honeypot -> rate-limit ->
// SMTP send -> forget. No persistence, no message bodies logged.

var builder = WebApplication.CreateBuilder(args);

var cfg = builder.Configuration;
var allowedOrigin = cfg["Relay:AllowedOrigin"] ?? "https://cesnimda.co.uk";
var rateLimit = cfg.GetValue("Relay:RateLimitPerWindow", 5);
var windowSeconds = cfg.GetValue("Relay:WindowSeconds", 600);

builder.Services.ConfigureHttpJsonOptions(o =>
    o.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonContext.Default));

// Real client IP from our own reverse proxy.
builder.Services.Configure<ForwardedHeadersOptions>(o =>
{
    o.ForwardedHeaders = ForwardedHeaders.XForwardedFor;
    o.KnownNetworks.Clear();
    o.KnownProxies.Clear();
});

builder.Services.AddCors(o => o.AddPolicy("site", p =>
    p.WithOrigins(allowedOrigin).WithMethods("POST").WithHeaders("Content-Type")));

builder.Services.AddRateLimiter(o =>
{
    o.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
    o.AddPolicy("contact", ctx => RateLimitPartition.GetFixedWindowLimiter(
        ctx.Connection.RemoteIpAddress?.ToString() ?? "unknown",
        _ => new FixedWindowRateLimiterOptions
        {
            PermitLimit = rateLimit,
            Window = TimeSpan.FromSeconds(windowSeconds),
            QueueLimit = 0,
        }));
});

var app = builder.Build();
app.UseForwardedHeaders();
app.UseCors("site");
app.UseRateLimiter();

app.MapGet("/healthz", () => Results.Ok("ok"));

app.MapPost("/api/contact", async (ContactRequest req, HttpContext http, ILogger<Program> log) =>
{
    var ipHash = HashIp(http.Connection.RemoteIpAddress?.ToString());

    // Honeypot: a filled hidden field means a bot. Feign success, send nothing.
    if (!string.IsNullOrWhiteSpace(req.Company))
    {
        log.LogInformation("contact rejected (honeypot) ip={Ip}", ipHash);
        return Results.Ok(new ContactResponse(true));
    }

    var name = (req.Name ?? "").Trim();
    var email = (req.Email ?? "").Trim();
    var message = (req.Message ?? "").Trim();

    if (name.Length is 0 or > 200 ||
        email.Length is 0 or > 320 || !IsEmail(email) ||
        message.Length is 0 or > 5000)
    {
        return Results.BadRequest(new ContactResponse(false, "invalid"));
    }

    try
    {
        await SendAsync(cfg, name, email, message);
        log.LogInformation("contact sent ip={Ip}", ipHash);
        return Results.Ok(new ContactResponse(true));
    }
    catch (Exception ex)
    {
        log.LogError(ex, "contact send failed ip={Ip}", ipHash);
        return Results.StatusCode(StatusCodes.Status502BadGateway);
    }
}).RequireRateLimiting("contact");

app.Run();

static bool IsEmail(string s)
{
    var at = s.IndexOf('@');
    var dot = s.LastIndexOf('.');
    return at > 0 && dot > at + 1 && dot < s.Length - 1 && !s.Contains(' ');
}

static string HashIp(string? ip)
{
    if (string.IsNullOrEmpty(ip)) return "unknown";
    var bytes = SHA256.HashData(Encoding.UTF8.GetBytes(ip));
    return Convert.ToHexString(bytes, 0, 4); // short, non-reversible marker
}

static async Task SendAsync(IConfiguration cfg, string name, string email, string message)
{
    var host = cfg["Smtp:Host"] ?? throw new InvalidOperationException("Smtp:Host not configured");
    var port = cfg.GetValue("Smtp:Port", 587);
    var user = cfg["Smtp:User"];
    var password = cfg["Smtp:Password"];
    var to = cfg["Relay:ToAddress"] ?? throw new InvalidOperationException("Relay:ToAddress not configured");
    var from = cfg["Relay:FromAddress"] ?? user ?? to;

    using var msg = new MailMessage
    {
        From = new MailAddress(from, "Portfolio contact form"),
        Subject = $"Portfolio contact from {name}",
        Body =
            $"From: {name} <{email}>\n\n{message}\n\n— sent via cesnimda.co.uk contact form",
        BodyEncoding = Encoding.UTF8,
        SubjectEncoding = Encoding.UTF8,
    };
    msg.To.Add(to);
    msg.ReplyToList.Add(new MailAddress(email, name));

    using var client = new SmtpClient(host, port)
    {
        EnableSsl = true,
        DeliveryMethod = SmtpDeliveryMethod.Network,
        Credentials = string.IsNullOrEmpty(user) ? null : new NetworkCredential(user, password),
    };
    await client.SendMailAsync(msg);
}

record ContactRequest(
    [property: JsonPropertyName("name")] string? Name,
    [property: JsonPropertyName("email")] string? Email,
    [property: JsonPropertyName("message")] string? Message,
    [property: JsonPropertyName("company")] string? Company);

record ContactResponse(bool Ok, string? Error = null);

[JsonSerializable(typeof(ContactRequest))]
[JsonSerializable(typeof(ContactResponse))]
partial class AppJsonContext : JsonSerializerContext;
