using EonaCat.LogStack.Status.Data;
using EonaCat.LogStack.Status.Models;
using EonaCat.LogStack.Status.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System.Text.Json;

namespace EonaCat.LogStack.Status.Controllers;

// This file is part of the EonaCat project(s) which is released under the Apache License.
// See the LICENSE file or go to https://EonaCat.com/License for full license details.

[ApiController]
[Route("api")]
public class ApiController : ControllerBase
{
    private readonly DatabaseContext _database;
    private readonly MonitoringService _monitorService;
    private readonly IngestionService _ingestionService;

    public ApiController(DatabaseContext database, MonitoringService monitorService, IngestionService ingestionService)
    {
        _database = database;
        _monitorService = monitorService;
        _ingestionService = ingestionService;
    }

    [HttpGet("status/summary")]
    public async Task<IActionResult> GetSummary()
    {
        var isAdmin = IsAdmin();
        var stats = await _monitorService.GetStatsAsync(isAdmin);
        return Ok(stats);
    }

    [HttpGet("monitors")]
    public async Task<IActionResult> GetMonitors()
    {
        var isAdmin = IsAdmin();
        var query = _database.Monitors.Where(m => m.IsActive);
        if (!isAdmin)
        {
            query = query.Where(m => m.IsPublic);
        }

        return Ok(await query.ToListAsync());
    }

    [HttpGet("monitors/{id}")]
    public async Task<IActionResult> GetMonitor(int id)
    {
        var monitor = await _database.Monitors.FindAsync(id);
        if (monitor == null)
        {
            return NotFound();
        }

        if (!IsAdmin() && !monitor.IsPublic)
        {
            return Unauthorized();
        }

        return Ok(monitor);
    }

    [HttpGet("monitors/{id}/check")]
    public async Task<IActionResult> CheckMonitor(int id)
    {
        var monitor = await _database.Monitors.FindAsync(id);
        if (monitor == null)
        {
            return NotFound();
        }

        var check = await _monitorService.CheckMonitorAsync(monitor);
        return Ok(check);
    }

    /// <summary>Returns the last N checks for a monitor (default 100).</summary>
    [HttpGet("monitors/{id}/history")]
    public async Task<IActionResult> GetMonitorHistory(int id, [FromQuery] int limit = 100)
    {
        var monitor = await _database.Monitors.FindAsync(id);
        if (monitor == null)
        {
            return NotFound();
        }

        if (!IsAdmin() && !monitor.IsPublic)
        {
            return Unauthorized();
        }

        var checks = await _database.MonitorChecks
            .Where(c => c.MonitorId == id)
            .OrderByDescending(c => c.CheckedAt)
            .Take(Math.Clamp(limit, 1, 1000))
            .ToListAsync();

        return Ok(checks);
    }

    /// <summary>Returns uptime percentages over 24h / 7d / 30d windows.</summary>
    [HttpGet("monitors/{id}/uptime")]
    public async Task<IActionResult> GetMonitorUptime(int id)
    {
        var monitor = await _database.Monitors.FindAsync(id);
        if (monitor == null)
        {
            return NotFound();
        }

        if (!IsAdmin() && !monitor.IsPublic)
        {
            return Unauthorized();
        }

        var report = await _monitorService.GetUptimeReportAsync(id);
        return Ok(report);
    }

    /// <summary>Pause or resume a monitor (admin only).</summary>
    [HttpPost("monitors/{id}/pause")]
    public async Task<IActionResult> PauseMonitor(int id)
    {
        if (!IsAdmin())
        {
            return Unauthorized();
        }

        var monitor = await _database.Monitors.FindAsync(id);
        if (monitor == null)
        {
            return NotFound();
        }

        monitor.IsActive = false;
        await _database.SaveChangesAsync();
        return Ok(new { id, active = false });
    }

    [HttpPost("monitors/{id}/resume")]
    public async Task<IActionResult> ResumeMonitor(int id)
    {
        if (!IsAdmin())
        {
            return Unauthorized();
        }

        var monitor = await _database.Monitors.FindAsync(id);
        if (monitor == null)
        {
            return NotFound();
        }

        monitor.IsActive = true;
        monitor.LastChecked = null; // force immediate re-check
        await _database.SaveChangesAsync();
        return Ok(new { id, active = true });
    }

    [HttpGet("monitors/{id}/alerts")]
    public async Task<IActionResult> GetAlertRules(int id)
    {
        if (!IsAdmin())
        {
            return Unauthorized();
        }

        var rules = await _database.AlertRules.Where(r => r.MonitorId == id).ToListAsync();
        return Ok(rules);
    }

    [HttpPost("monitors/{id}/alerts")]
    public async Task<IActionResult> CreateAlertRule(int id, [FromBody] AlertRule rule)
    {
        if (!IsAdmin())
        {
            return Unauthorized();
        }

        var monitor = await _database.Monitors.FindAsync(id);
        if (monitor == null)
        {
            return NotFound();
        }

        rule.MonitorId = id;
        _database.AlertRules.Add(rule);
        await _database.SaveChangesAsync();
        return Ok(rule);
    }

    [HttpDelete("alerts/{ruleId}")]
    public async Task<IActionResult> DeleteAlertRule(int ruleId)
    {
        if (!IsAdmin())
        {
            return Unauthorized();
        }

        var rule = await _database.AlertRules.FindAsync(ruleId);
        if (rule == null)
        {
            return NotFound();
        }

        _database.AlertRules.Remove(rule);
        await _database.SaveChangesAsync();
        return Ok(new { deleted = ruleId });
    }

    /// <summary>List incidents (admin sees all; public sees only IsPublic incidents).</summary>
    [HttpGet("incidents")]
    public async Task<IActionResult> GetIncidents([FromQuery] bool activeOnly = false)
    {
        var isAdmin = IsAdmin();
        var query = _database.Incidents.Include(i => i.Updates).AsQueryable();
        if (!isAdmin)
        {
            query = query.Where(i => i.IsPublic);
        }

        if (activeOnly)
        {
            query = query.Where(i => i.Status != IncidentStatus.Resolved);
        }

        var list = await query.OrderByDescending(i => i.CreatedAt).ToListAsync();
        return Ok(list);
    }

    [HttpGet("incidents/{id}")]
    public async Task<IActionResult> GetIncident(int id)
    {
        var incident = await _database.Incidents.Include(i => i.Updates).FirstOrDefaultAsync(i => i.Id == id);
        if (incident == null)
        {
            return NotFound();
        }

        if (!IsAdmin() && !incident.IsPublic)
        {
            return Unauthorized();
        }

        return Ok(incident);
    }

    [HttpPost("incidents")]
    public async Task<IActionResult> CreateIncident([FromBody] Incident incident)
    {
        if (!IsAdmin())
        {
            return Unauthorized();
        }

        incident.CreatedAt = DateTime.UtcNow;
        incident.UpdatedAt = DateTime.UtcNow;
        _database.Incidents.Add(incident);
        await _database.SaveChangesAsync();
        return Ok(incident);
    }

    [HttpPatch("incidents/{id}")]
    public async Task<IActionResult> UpdateIncident(int id, [FromBody] IncidentPatchDto patch)
    {
        if (!IsAdmin())
        {
            return Unauthorized();
        }

        var incident = await _database.Incidents.FindAsync(id);
        if (incident == null)
        {
            return NotFound();
        }

        if (patch.Status.HasValue)
        {
            incident.Status = patch.Status.Value;
            if (patch.Status == IncidentStatus.Resolved)
            {
                incident.ResolvedAt = DateTime.UtcNow;
            }
        }
        if (patch.Severity.HasValue)
        {
            incident.Severity = patch.Severity.Value;
        }

        if (patch.Body != null)
        {
            incident.Body = patch.Body;
        }

        incident.UpdatedAt = DateTime.UtcNow;

        if (!string.IsNullOrWhiteSpace(patch.UpdateMessage))
        {
            _database.IncidentUpdates.Add(new IncidentUpdate
            {
                IncidentId = id,
                Message = patch.UpdateMessage,
                Status = patch.Status ?? incident.Status
            });
        }

        await _database.SaveChangesAsync();
        return Ok(incident);
    }

    [HttpDelete("incidents/{id}")]
    public async Task<IActionResult> DeleteIncident(int id)
    {
        if (!IsAdmin())
        {
            return Unauthorized();
        }

        var incident = await _database.Incidents.FindAsync(id);
        if (incident == null)
        {
            return NotFound();
        }

        _database.Incidents.Remove(incident);
        await _database.SaveChangesAsync();
        return Ok(new { deleted = id });
    }

    [HttpGet("logs")]
    public async Task<IActionResult> QueryLogs(
        [FromQuery] string? level,
        [FromQuery] string? source,
        [FromQuery] string? search,
        [FromQuery] DateTime? from,
        [FromQuery] DateTime? to,
        [FromQuery] int page = 1,
        [FromQuery] int pageSize = 100)
    {
        if (!IsAdmin())
        {
            return Unauthorized();
        }

        var query = _database.Logs.AsQueryable();
        if (!string.IsNullOrWhiteSpace(level))
        {
            query = query.Where(x => x.Level == level.ToLower());
        }

        if (!string.IsNullOrWhiteSpace(source))
        {
            query = query.Where(x => x.Source == source);
        }

        if (!string.IsNullOrWhiteSpace(search))
        {
            query = query.Where(x => x.Message.Contains(search));
        }

        if (from.HasValue)
        {
            query = query.Where(x => x.Timestamp >= from.Value);
        }

        if (to.HasValue)
        {
            query = query.Where(x => x.Timestamp <= to.Value);
        }

        var total = await query.CountAsync();
        var entries = await query
            .OrderByDescending(x => x.Timestamp)
            .Skip((page - 1) * pageSize)
            .Take(pageSize)
            .ToListAsync();

        return Ok(new { total, page, pageSize, entries });
    }

    /// <summary>Returns hourly log volume buckets for charting.</summary>
    [HttpGet("logs/stats")]
    public async Task<IActionResult> GetLogStats([FromQuery] int hours = 24)
    {
        if (!IsAdmin())
        {
            return Unauthorized();
        }

        var buckets = await _monitorService.GetLogStatsAsync(Math.Clamp(hours, 1, 168));
        return Ok(buckets);
    }

    [HttpPost("logs/ingest")]
    public async Task<IActionResult> IngestLog([FromBody] LogEntry entry)
    {
        if (string.IsNullOrWhiteSpace(entry.Message))
        {
            return BadRequest("message required");
        }

        entry.Level = (entry.Level ?? "info").ToLower();
        entry.Timestamp = DateTime.UtcNow;
        await _ingestionService.IngestAsync(entry);
        return Ok(new { success = true });
    }

    [HttpPost("logs/batch")]
    public async Task<IActionResult> IngestBatch([FromBody] List<LogEntry> entries)
    {
        if (entries == null || !entries.Any())
        {
            return BadRequest("entries required");
        }

        foreach (var entry in entries)
        {
            entry.Level = (entry.Level ?? "info").ToLower();
            if (entry.Timestamp == default)
            {
                entry.Timestamp = DateTime.UtcNow;
            }
        }
        await _ingestionService.IngestBatchAsync(entries);
        return Ok(new { success = true, count = entries.Count });
    }

    [HttpPost("logs/eonacat")]
    public async Task<IActionResult> IngestEonaCat([FromBody] object[] events)
    {
        var entries = new List<LogEntry>();
        foreach (var evtObj in events)
        {
            var json = JsonSerializer.Serialize(evtObj);
            var dict = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(json)!;
            var logEntry = new LogEntry
            {
                Source = dict.TryGetValue("properties", out var props) &&
                         props.ValueKind == JsonValueKind.Object &&
                         props.Deserialize<Dictionary<string, object?>>()!.TryGetValue("Application", out var appObj)
                         ? appObj?.ToString() ?? "EonaCat.LogStack" : "EonaCat.LogStack",
                Level = dict.TryGetValue("level", out var level) ? level.GetString() ?? "Info" : "Info",
                Message = dict.TryGetValue("message", out var msg) ? msg.GetString() ?? "" : "",
                Exception = dict.TryGetValue("exception", out var ex) ? ex.ToString() : null,
                Host = dict.TryGetValue("host", out var host) ? host.GetString() : null,
                TraceId = dict.TryGetValue("traceId", out var traceId) ? traceId.GetString() : null,
                Properties = dict.TryGetValue("properties", out var properties) ? properties.GetRawText() : null,
                Timestamp = dict.TryGetValue("timestamp", out var ts) && DateTime.TryParse(ts.GetString(), out var dt) ? dt : DateTime.UtcNow
            };
            logEntry.Level = MapEonaCatLevel(logEntry.Level);
            entries.Add(logEntry);
        }
        if (entries.Any())
        {
            await _ingestionService.IngestBatchAsync(entries);
        }

        return Ok(new { success = true, count = entries.Count });
    }

    [HttpPost("logs/serilog")]
    public async Task<IActionResult> IngestSerilog([FromBody] SerilogPayload payload)
    {
        var entries = payload.Events?.Select(e => new LogEntry
        {
            Source = e.Properties?.TryGetValue("Application", out var app) == true ? app?.ToString() ?? "serilog" : "serilog",
            Level = MapSerilogLevel(e.Level),
            Message = e.RenderedMessage ?? e.MessageTemplate ?? "",
            Exception = e.Exception,
            Properties = e.Properties != null ? JsonSerializer.Serialize(e.Properties) : null,
            Timestamp = e.Timestamp == default ? DateTime.UtcNow : e.Timestamp
        }).ToList() ?? new List<LogEntry>();

        if (entries.Any())
        {
            await _ingestionService.IngestBatchAsync(entries);
        }

        return Ok(new { success = true, count = entries.Count });
    }

    private bool IsAdmin() => HttpContext.Session.GetString("IsAdmin") == "true";

    private static string MapSerilogLevel(string? l) => l?.ToLower() switch
    {
        "verbose" or "debug" => "debug",
        "information" => "info",
        "warning" => "warn",
        "error" => "error",
        "fatal" => "critical",
        _ => "info"
    };

    private static string MapEonaCatLevel(string? l) => l?.ToLower() switch
    {
        "trace" or "debug" => "debug",
        "information" => "info",
        "warning" => "warn",
        "error" => "error",
        "critical" => "critical",
        _ => "info"
    };
}

public class IncidentPatchDto
{
    public IncidentStatus? Status { get; set; }
    public IncidentSeverity? Severity { get; set; }
    public string? Body { get; set; }
    /// <summary>If provided, a new IncidentUpdate is appended with this message.</summary>
    public string? UpdateMessage { get; set; }
}

public class EonaCatPayLoad
{
    public List<EonaCatLogEvent>? Events { get; set; }
}

public class EonaCatLogEvent
{
    public string Timestamp { get; set; } = default!;
    public string Level { get; set; } = default!;
    public string Message { get; set; } = default!;
    public string Category { get; set; } = default!;
    public int ThreadId { get; set; }
    public string? TraceId { get; set; }
    public string? SpanId { get; set; }
    public ExceptionDto? Exception { get; set; }
    public Dictionary<string, object?>? Properties { get; set; }
}

public class ExceptionDto
{
    public string Type { get; set; } = default!;
    public string Message { get; set; } = default!;
    public string? StackTrace { get; set; }
}

public class SerilogPayload
{
    public List<SerilogEvent>? Events { get; set; }
}

public class SerilogEvent
{
    public DateTime Timestamp { get; set; }
    public string? Level { get; set; }
    public string? MessageTemplate { get; set; }
    public string? RenderedMessage { get; set; }
    public string? Exception { get; set; }
    public Dictionary<string, object?>? Properties { get; set; }
}
