using System.Globalization;
using System.Linq;
using System.Text.Json;
using CsvHelper;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.ObjectPool;

var builder = WebApplication.CreateBuilder(args);
builder.WebHost.ConfigureKestrel(serverOptions =>
{
    serverOptions.Listen(System.Net.IPAddress.Any, 7860); // Listen on all network interfaces on port 5000
});

builder.Services.AddDbContext<QuoteDbContext>(options =>
    options.UseSqlite("Data Source=quotes.db"));

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: "CorsPolicy", builder =>
    {
        builder.AllowAnyOrigin()
            .AllowAnyHeader()
            .AllowAnyMethod();
    });
});

builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

// Configure the HTTP request pipeline.
//if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

const int MaxPageSize = 100;

app.UseCors();

app.MapGet("/quotes", async (QuoteDbContext db, int pageNumber = 1, int pageSize = 10, [FromQuery] DataSetSources dataset = DataSetSources.all) =>
{
    if (!Enum.IsDefined(typeof(DataSetSources), dataset))
        return Results.BadRequest("Invalid dataset.");

    if (pageNumber < 1) pageNumber = 1;
    if (pageSize < 1) pageSize = 10;
    pageSize = Math.Min(pageSize, MaxPageSize); // Limit pageSize to MaxPageSize

    var quotes = GlobalData.Quotes;
    if (dataset != DataSetSources.all)
    {
        quotes = quotes.Where(x => x.DataSet == dataset.ToString()).ToList();
    }
    quotes = quotes
        .Skip((pageNumber - 1) * pageSize)
        .Take(pageSize)
        .ToList();

    return Results.Ok(quotes);
});

// app.MapGet("/quotes/{id}", async (int id, QuoteDbContext db) =>
//     await db.Quotes.FindAsync(id) is Quote quote
//         ? Results.Ok(quote)
//         : Results.NotFound("Quote not found"));

// app.MapPost("/quotes", async (Quote newQuote, QuoteDbContext db) =>
// {
//     db.Quotes.Add(newQuote);
//     await db.SaveChangesAsync();
//     return Results.Created($"/quotes/{newQuote.Id}", newQuote);
// });

// app.MapPut("/quotes/{id}", async (int id, Quote updatedQuote, QuoteDbContext db) =>
// {
//     var quote = await db.Quotes.FindAsync(id);
//     if (quote is null) return Results.NotFound("Quote not found");

//     quote.Author = updatedQuote.Author;
//     quote.QuoteText = updatedQuote.QuoteText;
//     quote.Source = updatedQuote.Source;
//     quote.Book = updatedQuote.Book;
//     quote.Categories = updatedQuote.Categories;
//     quote.Url = updatedQuote.Url;
//     quote.Isbn = updatedQuote.Isbn;
//     quote.Language = updatedQuote.Language;
//     quote.OriginalLanguage = updatedQuote.OriginalLanguage;

//     await db.SaveChangesAsync();
//     return Results.NoContent();
// });

// app.MapDelete("/quotes/{id}", async (int id, QuoteDbContext db) =>
// {
//     var quote = await db.Quotes.FindAsync(id);
//     if (quote is null) return Results.NotFound("Quote not found");

//     db.Quotes.Remove(quote);
//     await db.SaveChangesAsync();
//     return Results.NoContent();
// });

// Random quote endpoint
app.MapGet("/quotes/random", async (QuoteDbContext db, [FromQuery] DataSetSources dataset = DataSetSources.all, [FromQuery] string tags = "") =>
{
    if (!Enum.IsDefined(typeof(DataSetSources), dataset))
        return Results.BadRequest("Invalid dataset.");

    int quoteCount = 0;
    var quotes = GlobalData.Quotes;
    
    var tagsList = new List<string>();
    if (!string.IsNullOrEmpty(tags))
    {
        try {
            tagsList = tags.Split('|', StringSplitOptions.RemoveEmptyEntries).Select(x => x.ToLower()).ToList();
        } catch (Exception) {
            return Results.BadRequest("Invalid tags.");
        }
    }

    if (tagsList.Count > 0)
    {
        quotes = quotes.Where(q => !string.IsNullOrEmpty(q.Categories) && q.Categories.Split(',').Any(x => tagsList.Select(y => y.ToLower()).Contains(x))).ToList();
    }

    Quote? randomQuote = null;
    
    
    if (dataset != DataSetSources.all)
    {
        quoteCount = quotes.Count(q => q.DataSet == dataset.ToString());
    }
    else
    {
        quoteCount = quotes.Count(); //await db.Quotes.CountAsync();
    }

    if (quoteCount == 0)
        return Results.NotFound("No quotes available.");

    var random = new Random();
    var randomIndex = random.Next(quoteCount);
    
    if (dataset != DataSetSources.all)
    {
        randomQuote = quotes
            .Where(q => q.DataSet == dataset.ToString())
            .Skip(randomIndex)
            .Take(1)
            .FirstOrDefault();
    }
    else
    {
        randomQuote = quotes[randomIndex]; //await db.Quotes.Skip(randomIndex).Take(1).FirstOrDefaultAsync();
    }
    return randomQuote != null ? Results.Ok(randomQuote) : Results.NotFound("No quote found.");
});

// Search quotes by author, categories, book, or quoteText with pagination
app.MapGet("/quotes/search", async (string query, QuoteDbContext db, int pageNumber = 1, int pageSize = 10, [FromQuery] DataSetSources dataset = DataSetSources.all) =>
{
    if (!Enum.IsDefined(typeof(DataSetSources), dataset))
        return Results.BadRequest("Invalid dataset.");

    var queryQuotes = GlobalData.Quotes.AsQueryable(); //db.Quotes.AsQueryable();

    if (dataset != DataSetSources.all)
    {
        queryQuotes = queryQuotes.Where(q => q.DataSet == dataset.ToString());
    }

    if (!string.IsNullOrWhiteSpace(query))
    {
        query = query.ToLower();
        queryQuotes = queryQuotes.Where(q =>
            q.Author.ToLower().Contains(query) ||
            (q.Categories != null && q.Categories.ToLower().Contains(query)) ||
            (q.Book != null && q.Book.ToLower().Contains(query)) ||
            q.QuoteText.ToLower().Contains(query)
        );
    }

    if (pageNumber < 1) pageNumber = 1;
    if (pageSize < 1) pageSize = 10;
    pageSize = Math.Min(pageSize, MaxPageSize); // Limit pageSize to MaxPageSize

    var paginatedQuotes = queryQuotes
        .Skip((pageNumber - 1) * pageSize)
        .Take(pageSize)
        .ToList();

    return paginatedQuotes.Any() ? Results.Ok(paginatedQuotes) : Results.NotFound("No matching quotes found.");
});

app.MapGet("/health", () => Results.Ok(new { Status = "Healthy", Timestamp = DateTime.UtcNow }));
app.MapGet("/", () => Results.Ok(new { message = "Hello!", Timestamp = DateTime.UtcNow }));

async Task SaveSource1Async(string jsonFilePath, QuoteDbContext db)
{
    var path = Path.Combine(Directory.GetCurrentDirectory(), "data", jsonFilePath);

    if (!File.Exists(path))
        throw new FileNotFoundException("The JSON file for seeding is missing.");

    // Fields
    // ==========
    // Quote (string)
    // Author (string)
    // Tags (list<string>)
    // Category (string)

    var jsonString = await File.ReadAllTextAsync(path);
    // TODO: import data and sanitize relevant fields: Trim and Trim('"')
    var quotes = JsonSerializer.Deserialize<List<MittalInput>>(jsonString);
    var uniqueQuotes = quotes.DistinctBy(x => x.Content).ToList();
    foreach (var quote in uniqueQuotes)
    {
        var q = quote.GetQuote1();
        if (!db.Quotes.Any(x => x.QuoteText == q.QuoteText.CleanString()))
        {
            db.Quotes.Add(q);
            await db.SaveChangesAsync();
        }
    }
}

async Task SaveSource2Async(string jsonFile, QuoteDbContext db)
{
    var path = Path.Combine(Directory.GetCurrentDirectory(), "data", jsonFile);

    if (!File.Exists(path))
        throw new FileNotFoundException("The JSON file for seeding is missing.");

    // Fields
    // ==========
    // content (string)
    // author (string)
    // tags (list<string>)

    var jsonString = await File.ReadAllTextAsync(path);
    // TODO: import data and sanitize relevant fields: Trim and Trim('"')
    var quotes = JsonSerializer.Deserialize<List<QuotableInput>>(jsonString);
    var uniqueQuotes = quotes.DistinctBy(x => x.Content).ToList();
    foreach (var quote in uniqueQuotes)
    {
        var q = quote.GetQuote2();
        if (!db.Quotes.Any(x => x.QuoteText == q.QuoteText.CleanString()))
        {
            db.Quotes.Add(q);
            await db.SaveChangesAsync();
        }
    }
}

async Task SaveSource3Async(string csvFile, QuoteDbContext db)
{
    var path = Path.Combine(Directory.GetCurrentDirectory(), "data", csvFile);

    if (!File.Exists(path))
        throw new FileNotFoundException("The CSV file for seeding is missing.");

    // Fields
    // ==========
    // quote (string)
    // author (string)
    // category (string)

    //read csv file into list of records
    var reader = new StreamReader(path);
    var csv = new CsvReader(reader, CultureInfo.InvariantCulture);
    var records = csv.GetRecords<ManannInput>().ToList();
    foreach (var record in records)
    {
        var q = record.GetQuote3();
        if (!db.Quotes.Any(x => x.QuoteText == q.QuoteText.CleanString()))
        {
            db.Quotes.Add(q);
            await db.SaveChangesAsync();
        }
    }
}

// Seed database
async Task SeedDatabase(QuoteDbContext db)
{
    if (await db.Quotes.AnyAsync())
        return; // Database is already seeded

    await SaveSource1Async("source1.json", db);
    await SaveSource2Async("source2.json", db);
    await SaveSource3Async("source3.csv", db);
}

using (var scope = app.Services.CreateScope())
{
    var db = scope.ServiceProvider.GetRequiredService<QuoteDbContext>();
    //db.Database.EnsureCreated();
    GlobalData.Quotes = await db.Quotes.ToListAsync();
    //await SeedDatabase(db);
}

app.Run();