using JobTrackerApi.Models;
using JobTrackerApi.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;

namespace JobTrackerApi.Controllers;

// Phase 4 — Career Workspace Builder API. Every endpoint reads the master profile through
// ICareerProfileService and renders through the data-driven theme engine; none of them store career
// data. docs/architecture/cv-builder.md.
[ApiController]
[Route("api/cv")]
[Authorize(AuthenticationSchemes = "local")]
public sealed class CvVariantController : ControllerBase
{
    private readonly UserManager<ApplicationUser> _users;
    private readonly ICvVariantService _variants;
    private readonly ICvPdfExporter _pdf;
    private readonly ISummarizerService _ai;
    private readonly IThemedCvRenderer _renderer;

    public CvVariantController(UserManager<ApplicationUser> users, ICvVariantService variants, ICvPdfExporter pdf, ISummarizerService ai, IThemedCvRenderer? renderer = null)
    {
        _users = users;
        _variants = variants;
        _pdf = pdf;
        _ai = ai;
        _renderer = renderer ?? new ThemedCvRenderer();
    }

    public sealed record VariantDto(int Id, string Name, CvVariantSettings Settings, bool IsPublic, string PublicSlug, int Version, int? JobApplicationId, DateTimeOffset UpdatedAtUtc);
    public sealed record CreateVariantRequest(string? Name, int? JobApplicationId, CvVariantSettings? Settings);
    public sealed record SaveVariantRequest(string? Name, CvVariantSettings? Settings, string? Source);
    public sealed record PublicToggleRequest(bool IsPublic);
    public sealed record DuplicateRequest(string? Name);
    public sealed record PreviewRequest(CvVariantSettings? Settings);
    public sealed record RenderDto(string ThemeId, string Html, string SuggestedFileName);
    public sealed record AiAssistRequest(string? Action, string? Text, string? Context, string? Role, string? Language);
    public sealed record AiAssistResult(string Original, string Result);

    [HttpGet("themes")]
    public async Task<ActionResult<IEnumerable<object>>> GetThemes()
    {
        var user = await _users.GetUserAsync(User);
        if (user is null) return Unauthorized();
        var proThemes = AccountPlans.ForRoles(await _users.GetRolesAsync(user)).ProThemes;
        var themes = CvThemeCatalog.Themes.Select(t => new
        {
            id = t.Id,
            name = t.Name,
            category = t.Category,
            description = t.Description,
            layout = t.Layout,
            accent = t.Accent,
            photoShape = t.PhotoShape,
            supportsIcons = t.DefaultIcons,
            atsFriendly = t.AtsFriendly,
            requiresPro = t.Premium,
            available = proThemes || !t.Premium,
            swatches = new[] { t.Accent, t.SidebarBg, t.Paper },
            supportedSettings = t.SupportedSettings,
            pageMarginMm = t.PageMarginMm,
        });
        return Ok(themes);
    }

    // Render-only sample for visual template thumbnails. This in-memory content is never written to
    // the authenticated user's master profile or to a CV variant.
    [HttpGet("themes/{themeId}/preview")]
    public async Task<ActionResult<RenderDto>> GetThemePreview(string themeId)
    {
        var user = await _users.GetUserAsync(User);
        if (user is null) return Unauthorized();
        if (!CvThemeCatalog.Exists(themeId)) return NotFound();
        var theme = CvThemeCatalog.Resolve(themeId);
        var settings = CvVariantSettingsJson.Normalize(new CvVariantSettings { ThemeId = theme.Id, ShowIcons = true });
        var render = _renderer.Render(TemplatePreviewModel(), theme, settings);
        return Ok(new RenderDto(render.ThemeId, render.Html, render.SuggestedFileName));
    }

    // The master profile as sections+entries (with ItemKeys) — the Content tab reads this to render
    // editable rows without duplicating the profile shape on the client.
    [HttpGet("outline")]
    public async Task<ActionResult<CvRenderModel>> Outline(CancellationToken ct)
    {
        var user = await _users.GetUserAsync(User);
        if (user is null) return Unauthorized();
        return Ok(await _variants.OutlineAsync(user.Id, Person(user), ct));
    }

    [HttpGet("variants")]
    public async Task<ActionResult<IEnumerable<CvVariantSummary>>> List(CancellationToken ct)
    {
        var user = await _users.GetUserAsync(User);
        if (user is null) return Unauthorized();
        return Ok(await _variants.ListAsync(user.Id, ct));
    }

    [HttpPost("variants")]
    public async Task<ActionResult<VariantDto>> Create([FromBody] CreateVariantRequest? request, CancellationToken ct)
    {
        var user = await _users.GetUserAsync(User);
        if (user is null) return Unauthorized();
        if (request?.Settings is not null && !CvThemeCatalog.Exists(request.Settings.ThemeId))
            return BadRequest("Unknown theme.");
        if (request?.Settings is not null && !await CanUseThemeAsync(user, request.Settings.ThemeId))
            return StatusCode(StatusCodes.Status403Forbidden, "This theme requires Pro.");
        if (request?.JobApplicationId is int jobApplicationId && !await _variants.CanAssociateJobAsync(user.Id, jobApplicationId, ct))
            return BadRequest("The job application is unavailable.");
        var variant = await _variants.CreateAsync(user.Id, request?.Name, request?.JobApplicationId, request?.Settings, ct);
        return Ok(ToDto(variant));
    }

    [HttpGet("variants/{id:int}")]
    public async Task<ActionResult<VariantDto>> Get(int id, CancellationToken ct)
    {
        var user = await _users.GetUserAsync(User);
        if (user is null) return Unauthorized();
        var variant = await _variants.GetAsync(user.Id, id, ct);
        return variant is null ? NotFound() : Ok(ToDto(variant));
    }

    [HttpPut("variants/{id:int}")]
    public async Task<ActionResult<VariantDto>> Save(int id, [FromBody] SaveVariantRequest request, CancellationToken ct)
    {
        var user = await _users.GetUserAsync(User);
        if (user is null) return Unauthorized();
        var settings = CvVariantSettingsJson.Normalize(request.Settings);
        if (!CvThemeCatalog.Exists(settings.ThemeId)) return BadRequest("Unknown theme.");
        if (!await CanUseThemeAsync(user, settings.ThemeId))
        {
            var current = await _variants.GetAsync(user.Id, id, ct);
            if (current is null) return NotFound();
            if (!string.Equals(CvVariantSettingsJson.Deserialize(current.SettingsJson).ThemeId, settings.ThemeId, StringComparison.OrdinalIgnoreCase))
                return StatusCode(StatusCodes.Status403Forbidden, "This theme requires Pro.");
        }
        var variant = await _variants.SaveAsync(user.Id, id, request.Name, settings, request.Source ?? "autosave", ct);
        return variant is null ? NotFound() : Ok(ToDto(variant));
    }

    [HttpDelete("variants/{id:int}")]
    public async Task<IActionResult> Delete(int id, CancellationToken ct)
    {
        var user = await _users.GetUserAsync(User);
        if (user is null) return Unauthorized();
        return await _variants.DeleteAsync(user.Id, id, ct) ? NoContent() : NotFound();
    }

    [HttpPost("variants/{id:int}/duplicate")]
    public async Task<ActionResult<VariantDto>> Duplicate(int id, [FromBody] DuplicateRequest? request, CancellationToken ct)
    {
        var user = await _users.GetUserAsync(User);
        if (user is null) return Unauthorized();
        var variant = await _variants.DuplicateAsync(user.Id, id, request?.Name, ct);
        return variant is null ? NotFound() : Ok(ToDto(variant));
    }

    [HttpPut("variants/{id:int}/public")]
    public async Task<ActionResult<VariantDto>> SetPublic(int id, [FromBody] PublicToggleRequest request, CancellationToken ct)
    {
        var user = await _users.GetUserAsync(User);
        if (user is null) return Unauthorized();
        var variant = await _variants.SetPublicAsync(user.Id, id, request.IsPublic, ct);
        return variant is null ? NotFound() : Ok(ToDto(variant));
    }

    [HttpGet("variants/{id:int}/versions")]
    public async Task<ActionResult<IEnumerable<CvVariantVersionInfo>>> Versions(int id, CancellationToken ct)
    {
        var user = await _users.GetUserAsync(User);
        if (user is null) return Unauthorized();
        return Ok(await _variants.ListVersionsAsync(user.Id, id, ct));
    }

    [HttpPost("variants/{id:int}/versions/{version:int}/restore")]
    public async Task<ActionResult<VariantDto>> Restore(int id, int version, CancellationToken ct)
    {
        var user = await _users.GetUserAsync(User);
        if (user is null) return Unauthorized();
        var variant = await _variants.RestoreVersionAsync(user.Id, id, version, ct);
        return variant is null ? NotFound() : Ok(ToDto(variant));
    }

    [HttpGet("variants/{id:int}/preview")]
    public async Task<ActionResult<RenderDto>> Preview(int id, CancellationToken ct)
    {
        var user = await _users.GetUserAsync(User);
        if (user is null) return Unauthorized();
        var render = await _variants.RenderAsync(user.Id, id, Person(user), ct);
        return render is null ? NotFound() : Ok(new RenderDto(render.ThemeId, render.Html, render.SuggestedFileName));
    }

    // Live preview for unsaved builder settings.
    [HttpPost("preview")]
    public async Task<ActionResult<RenderDto>> PreviewSettings([FromBody] PreviewRequest request, CancellationToken ct)
    {
        var user = await _users.GetUserAsync(User);
        if (user is null) return Unauthorized();
        var settings = CvVariantSettingsJson.Normalize(request.Settings);
        if (!CvThemeCatalog.Exists(settings.ThemeId)) return BadRequest("Unknown theme.");
        var render = await _variants.RenderSettingsAsync(user.Id, settings, Person(user), ct);
        return Ok(new RenderDto(render.ThemeId, render.Html, render.SuggestedFileName));
    }

    [HttpPost("variants/{id:int}/export-pdf")]
    public async Task<IActionResult> ExportPdf(int id, CancellationToken ct)
    {
        var user = await _users.GetUserAsync(User);
        if (user is null) return Unauthorized();
        var render = await _variants.RenderAsync(user.Id, id, Person(user), ct);
        if (render is null) return NotFound();
        var artifact = await _pdf.ExportAsync(user.Id, new TailoredCvRenderResult(render.ThemeId, render.SuggestedFileName, render.Html), ct);
        return File(artifact.Bytes, "application/pdf", artifact.FileName);
    }

    // AI assistance on any text area. Never mutates the profile or variant — returns a suggestion the
    // user reviews and applies themselves. Reuses the existing provider abstraction (ISummarizerService).
    [HttpPost("ai/assist")]
    [Authorize(Policy = ProEntitlement.Policy)]
    public async Task<ActionResult<AiAssistResult>> AiAssist([FromBody] AiAssistRequest request, CancellationToken ct)
    {
        var user = await _users.GetUserAsync(User);
        if (user is null) return Unauthorized();
        var text = (request.Text ?? string.Empty).Trim();
        if (text.Length == 0) return BadRequest("Provide text to work on.");
        if (text.Length > 60_000) return BadRequest("The requested CV content is too large to process in one operation.");

        var instruction = BuildAiInstruction(request.Action, request.Role, request.Language, request.Context);
        var isDocumentAction = (request.Action ?? string.Empty).StartsWith("document-", StringComparison.OrdinalIgnoreCase);
        var result = await _ai.SummarizeSectionAsync(instruction, text, isDocumentAction ? 16_000 : 1200, isDocumentAction ? 50 : 300);
        if (string.IsNullOrWhiteSpace(result))
        {
            var metrics = await _ai.GetMetricsAsync(ct);
            return StatusCode(StatusCodes.Status502BadGateway, metrics.Healthy
                ? "The AI service returned no usable text."
                : "The AI service is unavailable right now.");
        }
        return Ok(new AiAssistResult(text, result.Trim()));
    }

    private static string BuildAiInstruction(string? action, string? role, string? language, string? context)
    {
        var normalizedAction = (action ?? "improve").Trim().ToLowerInvariant();
        var target = string.IsNullOrWhiteSpace(role) ? null : role.Trim();
        var lang = string.IsNullOrWhiteSpace(language) ? "the same language as the input" : language.Trim();
        var extra = string.IsNullOrWhiteSpace(context) ? string.Empty : $" Context: {context.Trim()}.";
        var task = normalizedAction switch
        {
            "document-translate" => "Translate every user-facing CV value into the requested language. Return only valid JSON with the exact same array structure, ids and fields. Preserve names, employers, dates, qualifications, URLs and technical terms unless they have a standard translation.",
            "document-improve" => "Improve clarity, concision and professional impact across the CV. Return only valid JSON with the exact same array structure, ids and fields. Preserve every fact and never invent achievements, metrics, skills or experience.",
            "document-grammar" => "Correct spelling, grammar and punctuation across the CV without changing meaning. Return only valid JSON with the exact same array structure, ids and fields.",
            "professional" => "Rewrite the text in a more professional, confident tone.",
            "shorten" => "Make the text more concise without losing meaning.",
            "expand" => "Expand the text with more concrete, relevant detail — but never invent facts.",
            "grammar" => "Fix grammar, spelling and punctuation only. Keep wording and meaning.",
            "impact" => "Strengthen the text by identifying where the user could add measurable impact. Use clearly marked placeholders for missing numbers and never invent a metric or result.",
            "ats" => "Rewrite for ATS keyword optimisation: strong action verbs, quantified impact, clear phrasing. Do not invent facts.",
            "bullets" => "Rewrite as 3–5 tight achievement bullet points, each starting with a strong action verb. Do not invent facts.",
            "summary" => "Write a concise professional summary (2–3 sentences) from this text. Do not invent facts.",
            "rewrite" => "Rewrite the text more clearly and compellingly, preserving all facts.",
            "tailor" => $"Rewrite the text to align with the target role{(target is null ? string.Empty : $" '{target}'")}, emphasising the most relevant experience. Do not invent facts.",
            _ => "Improve the clarity, impact and phrasing of the text while preserving all facts.",
        };
        var outputInstruction = normalizedAction.StartsWith("document-", StringComparison.Ordinal)
            ? "Return only the JSON array with no Markdown fence or preamble."
            : "Return only the rewritten text with no preamble.";
        return $"{task} Preserve every factual claim — never invent employers, titles, dates, or metrics. Write in {lang}. {outputInstruction}{extra}";
    }

    private async Task<bool> CanUseThemeAsync(ApplicationUser user, string? themeId) =>
        CvThemeCatalog.CanUse(themeId, AccountPlans.ForRoles(await _users.GetRolesAsync(user)).ProThemes);

    private static CvRenderModel TemplatePreviewModel() => new()
    {
        FullName = "Alex Morgan",
        Headline = "Product & Platform Engineer",
        Contact =
        [
            new() { Icon = "email", Value = "alex@example.com", Href = "mailto:alex@example.com" },
            new() { Icon = "location", Value = "Oslo, Norway" },
            new() { Icon = "web", Value = "alexmorgan.dev", Href = "https://alexmorgan.dev" },
        ],
        Sections =
        [
            new() { Key = "summary", Title = "Profile", Kind = "bullets", Bullets = ["Engineer focused on clear products, resilient systems and measurable delivery."] },
            new()
            {
                Key = "experience", Title = "Experience", Kind = "entries",
                Entries =
                [
                    new() { Title = "Senior Platform Engineer", Subtitle = "Northstar Labs", Meta = "2022 – Present", Bullets = ["Led platform improvements across product teams.", "Reduced release lead time through safer automation."] },
                    new() { Title = "Software Engineer", Subtitle = "Studio Works", Meta = "2019 – 2022", Bullets = ["Built accessible customer workflows and APIs."] },
                ],
            },
            new() { Key = "skills", Title = "Skills", Kind = "tags", Tags = ["C#", "React", "Cloud", "Design systems", "Delivery"] },
            new() { Key = "education", Title = "Education", Kind = "entries", Entries = [new() { Title = "BSc Computer Science", Subtitle = "University of Oslo", Meta = "2016 – 2019" }] },
            new() { Key = "languages", Title = "Languages", Kind = "tags", Tags = ["English", "Norwegian"] },
        ],
    };

    private static CvRenderPerson Person(ApplicationUser user)
    {
        var name = string.Join(" ", new[] { user.FirstName?.Trim(), user.LastName?.Trim() }.Where(x => !string.IsNullOrWhiteSpace(x)));
        if (string.IsNullOrWhiteSpace(name)) name = user.DisplayName?.Trim();
        if (string.IsNullOrWhiteSpace(name)) name = user.UserName?.Trim();
        if (string.IsNullOrWhiteSpace(name)) name = user.Email?.Trim();
        if (string.IsNullOrWhiteSpace(name)) name = "Your Name";
        return new CvRenderPerson(name!, AvatarStorage.Resolve(user.AvatarImageDataUrl));
    }

    private static VariantDto ToDto(CvVariant v) => new(
        v.Id, v.Name, CvVariantSettingsJson.Deserialize(v.SettingsJson), v.IsPublic, v.PublicSlug, v.Version, v.JobApplicationId, v.UpdatedAtUtc);
}
