using System.Net.Http.Headers;
using System.Text.Json;

namespace Books.Api.AiBookkeeper;

/// <summary>
/// HTTP client for the AI Bookkeeper service.
/// </summary>
public class AiBookkeeperClient(HttpClient httpClient, ILogger<AiBookkeeperClient> logger) : IAiBookkeeperClient
{
    public async Task<AiBookkeeperResponse> ProcessDocumentAsync(
        Stream document,
        string fileName,
        string contentType,
        ChartOfAccountsDto chartOfAccounts,
        CancellationToken cancellationToken = default)
    {
        try
        {
            using var content = new MultipartFormDataContent();

            // Add the document file
            var documentContent = new StreamContent(document);
            documentContent.Headers.ContentType = new MediaTypeHeaderValue(contentType);
            content.Add(documentContent, "Documents", fileName);

            // Convert chart of accounts to .toon format and add as file
            var toonContent = ToonFormatConverter.ConvertToToon(chartOfAccounts);
            if (!string.IsNullOrWhiteSpace(toonContent))
            {
                var accountsBytes = System.Text.Encoding.UTF8.GetBytes(toonContent);
                var accountsStream = new MemoryStream(accountsBytes);
                var accountsContent = new StreamContent(accountsStream);
                accountsContent.Headers.ContentType = new MediaTypeHeaderValue("text/plain");
                content.Add(accountsContent, "AccountsFile", "accounts.toon");
            }

            logger.LogInformation(
                "Sending document {FileName} ({ContentType}) to AI Bookkeeper",
                fileName, contentType);

            var response = await httpClient.PostAsync("/api/v1/bookkeeping/process", content, cancellationToken);

            var responseBody = await response.Content.ReadAsStringAsync(cancellationToken);

            if (!response.IsSuccessStatusCode)
            {
                logger.LogWarning(
                    "AI Bookkeeper returned error {StatusCode}: {Body}",
                    response.StatusCode, responseBody);

                return new AiBookkeeperResponse
                {
                    Success = false,
                    ErrorMessage = $"AI service returned {response.StatusCode}: {responseBody}"
                };
            }

            logger.LogDebug("AI Bookkeeper response: {Response}", responseBody);

            return ParseResponse(responseBody);
        }
        catch (HttpRequestException ex)
        {
            logger.LogError(ex, "HTTP error calling AI Bookkeeper service");
            return new AiBookkeeperResponse
            {
                Success = false,
                ErrorMessage = "AI-tjenesten er midlertidigt utilgængelig"
            };
        }
        catch (TaskCanceledException ex) when (ex.InnerException is TimeoutException)
        {
            logger.LogError(ex, "Timeout calling AI Bookkeeper service");
            return new AiBookkeeperResponse
            {
                Success = false,
                ErrorMessage = "AI-tjenesten svarede ikke i tide"
            };
        }
        catch (Exception ex)
        {
            logger.LogError(ex, "Unexpected error calling AI Bookkeeper service");
            return new AiBookkeeperResponse
            {
                Success = false,
                ErrorMessage = "Der opstod en uventet fejl ved dokumentanalyse"
            };
        }
    }

    private AiBookkeeperResponse ParseResponse(string json)
    {
        using var doc = JsonDocument.Parse(json);
        var root = doc.RootElement;

        var result = new AiBookkeeperResponse
        {
            Success = root.TryGetProperty("success", out var successProp) && successProp.GetBoolean(),
            ErrorMessage = GetStringOrNull(root, "errorMessage")
        };

        // Parse extraction
        if (root.TryGetProperty("extraction", out var extraction) && extraction.ValueKind == JsonValueKind.Object)
        {
            result.Extraction = new DocumentExtraction
            {
                DocumentType = GetStringOrNull(extraction, "documentType"),
                Vendor = GetNestedStringOrDirect(extraction, "vendor", "name"),
                VendorCvr = GetNestedStringOrDirect(extraction, "vendor", "cvr"),
                InvoiceNumber = GetStringOrNull(extraction, "invoiceNumber"),
                Date = ParseDateOnly(GetStringOrNull(extraction, "date") ?? GetStringOrNull(extraction, "invoiceDate")),
                DueDate = ParseDateOnly(GetStringOrNull(extraction, "dueDate")),
                TotalAmount = GetDecimalOrNull(extraction, "totalAmount") ?? GetDecimalOrNull(extraction, "total"),
                AmountExVat = GetDecimalOrNull(extraction, "amountExVat") ?? GetDecimalOrNull(extraction, "subtotal"),
                VatAmount = GetDecimalOrNull(extraction, "vatAmount") ?? GetDecimalOrNull(extraction, "vat"),
                Currency = GetStringOrNull(extraction, "currency") ?? "DKK",
                PaymentReference = GetStringOrNull(extraction, "paymentReference"),
                RawText = GetStringOrNull(extraction, "rawText")
            };

            // Handle nested totals object (AI service may return totals as nested object)
            if (extraction.TryGetProperty("totals", out var totals) && totals.ValueKind == JsonValueKind.Object)
            {
                result.Extraction.TotalAmount ??= GetDecimalOrNull(totals, "grandTotal");
                result.Extraction.AmountExVat ??= GetDecimalOrNull(totals, "subtotal");
                result.Extraction.VatAmount ??= GetDecimalOrNull(totals, "vatTotal");
            }
        }

        // Parse suggested booking (full journal lines) or account suggestion (single account)
        if (root.TryGetProperty("suggestedBooking", out var booking) && booking.ValueKind == JsonValueKind.Object)
        {
            result.Suggestion = ParseBookingSuggestion(booking);
        }

        // Parse AI's single account suggestion (the smart recommendation)
        AiAccountSuggestion? aiAccountSuggestion = null;
        if (root.TryGetProperty("accountSuggestion", out var accountSuggestion) && accountSuggestion.ValueKind == JsonValueKind.Object)
        {
            aiAccountSuggestion = ParseAiAccountSuggestion(accountSuggestion);
        }

        // Generate journal entry lines from extraction data, using AI's account suggestion if available
        if ((result.Suggestion == null || result.Suggestion.Lines.Count == 0) && result.Extraction != null)
        {
            result.Suggestion = GenerateSuggestionFromExtraction(result.Extraction, aiAccountSuggestion);
        }

        return result;
    }

    /// <summary>
    /// Parses the AI service's single account suggestion.
    /// </summary>
    private static AiAccountSuggestion? ParseAiAccountSuggestion(JsonElement element)
    {
        var accountNumber = GetStringOrNull(element, "accountNumber");
        if (string.IsNullOrEmpty(accountNumber))
            return null;

        return new AiAccountSuggestion
        {
            AccountNumber = accountNumber,
            AccountName = GetStringOrNull(element, "accountName") ?? accountNumber,
            VatCode = GetStringOrNull(element, "vatCode"),
            Confidence = GetDecimalOrNull(element, "confidence") ?? 0.5m,
            Reasoning = GetStringOrNull(element, "reasoning")
        };
    }

    /// <summary>
    /// Generates a booking suggestion from extraction data.
    /// Uses AI's account suggestion for the expense line if available, otherwise falls back to generic Vareforbrug.
    /// Creates standard Danish bookkeeping entries for expense documents.
    /// </summary>
    private static BookkeepingSuggestion? GenerateSuggestionFromExtraction(
        DocumentExtraction extraction,
        AiAccountSuggestion? aiSuggestion = null)
    {
        // Need at least a total amount to generate suggestion
        if (!extraction.TotalAmount.HasValue || extraction.TotalAmount.Value <= 0)
            return null;

        // Use AI's confidence if available, otherwise lower confidence for fallback
        var overallConfidence = aiSuggestion?.Confidence ?? 0.7m;

        var suggestion = new BookkeepingSuggestion
        {
            Description = extraction.Vendor ?? "Udgift",
            Confidence = overallConfidence,
            Lines = []
        };

        var totalAmount = extraction.TotalAmount.Value;
        var amountExVat = extraction.AmountExVat ?? totalAmount;
        var vatAmount = extraction.VatAmount ?? 0;

        // Determine VAT code: use AI suggestion's VAT code, or calculate from amounts
        string? vatCode = aiSuggestion?.VatCode;
        if (vatCode == null && vatAmount > 0 && amountExVat > 0)
        {
            var vatRate = vatAmount / amountExVat;
            vatCode = vatRate >= 0.24m ? "I25" : null; // 25% indgaaende moms
        }

        // Expense line (debit) - Use AI's account suggestion if available
        // AI suggests specific account (e.g., 6080 Parkering, 7240 Telefoni)
        // Otherwise fall back to Erhvervsstyrelsen standard 1610 = Varekøb (maps to account 2000)
        var expenseAccountNumber = aiSuggestion?.AccountNumber ?? "1610";
        var expenseAccountName = aiSuggestion?.AccountName ?? "Vareforbrug";
        var expenseConfidence = aiSuggestion?.Confidence ?? 0.6m;

        suggestion.Lines.Add(new SuggestedLine
        {
            StandardAccountNumber = expenseAccountNumber,
            AccountName = expenseAccountName,
            DebitAmount = amountExVat,
            CreditAmount = 0,
            VatCode = vatCode,
            Confidence = expenseConfidence
        });

        // VAT line (debit to reduce liability / credit if balance is positive)
        // Erhvervsstyrelsen standard 7680 = Anden gæld til SKAT (maps to company account 7900 Skyldig moms)
        // For input VAT (indgående moms), we debit the moms account
        if (vatAmount > 0)
        {
            suggestion.Lines.Add(new SuggestedLine
            {
                StandardAccountNumber = "7680",
                AccountName = "Skyldig moms",
                DebitAmount = vatAmount,
                CreditAmount = 0,
                VatCode = null,
                Confidence = 0.9m
            });
        }

        // Creditor line (credit)
        // Erhvervsstyrelsen standard 7350 = Leverandører (maps to company account 6900)
        suggestion.Lines.Add(new SuggestedLine
        {
            StandardAccountNumber = "7350",
            AccountName = "Kreditorer",
            DebitAmount = 0,
            CreditAmount = totalAmount,
            VatCode = null,
            Confidence = 0.8m
        });

        return suggestion;
    }

    private static BookkeepingSuggestion ParseBookingSuggestion(JsonElement element)
    {
        var suggestion = new BookkeepingSuggestion
        {
            Description = GetStringOrNull(element, "description"),
            Confidence = GetDecimalOrNull(element, "confidence") ?? 0
        };

        // Parse lines from various possible properties
        JsonElement? linesElement = null;
        if (element.TryGetProperty("lines", out var lines))
            linesElement = lines;
        else if (element.TryGetProperty("entries", out var entries))
            linesElement = entries;

        if (linesElement.HasValue && linesElement.Value.ValueKind == JsonValueKind.Array)
        {
            foreach (var line in linesElement.Value.EnumerateArray())
            {
                suggestion.Lines.Add(new SuggestedLine
                {
                    StandardAccountNumber = GetStringOrNull(line, "standardAccountNumber") ?? GetStringOrNull(line, "accountNumber"),
                    AccountName = GetStringOrNull(line, "accountName") ?? GetStringOrNull(line, "account"),
                    DebitAmount = GetDecimalOrNull(line, "debit") ?? GetDecimalOrNull(line, "debitAmount") ?? 0,
                    CreditAmount = GetDecimalOrNull(line, "credit") ?? GetDecimalOrNull(line, "creditAmount") ?? 0,
                    VatCode = GetStringOrNull(line, "vatCode"),
                    Confidence = GetDecimalOrNull(line, "confidence") ?? 0
                });
            }
        }

        return suggestion;
    }

    private static string? GetStringOrNull(JsonElement element, string propertyName)
    {
        if (element.TryGetProperty(propertyName, out var prop))
        {
            return prop.ValueKind switch
            {
                JsonValueKind.String => prop.GetString(),
                JsonValueKind.Number => prop.GetRawText(),
                JsonValueKind.Null => null,
                _ => prop.GetRawText()
            };
        }
        return null;
    }

    private static string? GetNestedStringOrDirect(JsonElement element, string propertyName, string nestedPropertyName)
    {
        if (element.TryGetProperty(propertyName, out var prop))
        {
            if (prop.ValueKind == JsonValueKind.String)
                return prop.GetString();
            if (prop.ValueKind == JsonValueKind.Object && prop.TryGetProperty(nestedPropertyName, out var nested))
                return nested.ValueKind == JsonValueKind.String ? nested.GetString() : null;
        }
        return null;
    }

    private static decimal? GetDecimalOrNull(JsonElement element, string propertyName)
    {
        if (element.TryGetProperty(propertyName, out var prop) && prop.ValueKind == JsonValueKind.Number)
        {
            return prop.GetDecimal();
        }
        return null;
    }

    private static DateOnly? ParseDateOnly(string? dateStr)
    {
        if (string.IsNullOrEmpty(dateStr))
            return null;

        // Try various formats
        if (DateOnly.TryParse(dateStr, out var date))
            return date;

        // Try parsing just the date part if it contains time
        if (DateTime.TryParse(dateStr, out var dateTime))
            return DateOnly.FromDateTime(dateTime);

        return null;
    }
}
