using DbFirst.Application.Repositories;
using DbFirst.Domain;
using DbFirst.Domain.Entities;
using Microsoft.Data.SqlClient;
using Microsoft.EntityFrameworkCore;
using System.Data;

namespace DbFirst.Infrastructure.Repositories;

// TODO: instead of creating implementation of repository per entity, consider using generic repository pattern (eg. Repository<T>) to reduce code duplication.

/* Copilot's Response:

A generic Repository<T> isn’t really worthwhile here:
    • Reads from the view are generic, but inserts/updates/deletes go through stored procedures with special parameters/output GUIDs.You’d need lots of exceptions/overrides—little gain.
    • Operations aren’t symmetric (separate procs for insert/update/delete with output handling and reload), so a one-size-fits-all CRUD pattern doesn’t fit well.
    • Better to keep the specialized repo.If you want reuse, extract small helpers (e.g., for proc calls/output parameters/reload) instead of forcing a generic repository. */

public class CatalogRepository : ICatalogRepository
{
    private readonly ApplicationDbContext _db;

    public CatalogRepository(ApplicationDbContext db)
    {
        _db = db;
    }

    public async Task<List<VwmyCatalog>> GetAllAsync(CancellationToken cancellationToken = default)
    {
        return await _db.VwmyCatalogs.AsNoTracking().ToListAsync(cancellationToken);
    }

    public async Task<VwmyCatalog?> GetByIdAsync(int id, CancellationToken cancellationToken = default)
    {
        return await _db.VwmyCatalogs.AsNoTracking().FirstOrDefaultAsync(x => x.Guid == id, cancellationToken);
    }

    public async Task<VwmyCatalog?> GetByTitleAsync(string title, CancellationToken cancellationToken = default)
    {
        return await _db.VwmyCatalogs.AsNoTracking().FirstOrDefaultAsync(x => x.CatTitle == title, cancellationToken);
    }

    public async Task<VwmyCatalog> InsertAsync(VwmyCatalog catalog, CancellationToken cancellationToken = default)
    {
        var guidParam = new SqlParameter("@GUID", SqlDbType.Int)
        {
            Direction = ParameterDirection.Output
        };

        var catTitleParam = new SqlParameter("@CAT_TITLE", catalog.CatTitle);
        var catStringParam = new SqlParameter("@CAT_STRING", catalog.CatString);
        var addedWhoParam = new SqlParameter("@ADDED_WHO", (object?)catalog.AddedWho ?? DBNull.Value);

        await _db.Database.ExecuteSqlRawAsync(
            "EXEC dbo.PRTBMY_CATALOG_INSERT @CAT_TITLE, @CAT_STRING, @ADDED_WHO, @GUID OUTPUT",
            parameters: new[] { catTitleParam, catStringParam, addedWhoParam, guidParam },
            cancellationToken: cancellationToken);

        if (guidParam.Value == DBNull.Value)
        {
            throw new InvalidOperationException("Failed to insert catalog via stored procedure.");
        }

        var guid = (int)guidParam.Value;
        var created = await _db.VwmyCatalogs.AsNoTracking().FirstOrDefaultAsync(x => x.Guid == guid, cancellationToken);
        if (created == null)
        {
            throw new InvalidOperationException("Inserted catalog could not be loaded from view.");
        }

        return created;
    }

    public async Task<VwmyCatalog?> UpdateAsync(int id, VwmyCatalog catalog, CatalogUpdateProcedure procedure, CancellationToken cancellationToken = default)
    {
        catalog.Guid = id;

        var guidParam = new SqlParameter("@GUID", SqlDbType.Int)
        {
            Direction = ParameterDirection.Output
        };

        var catTitleParam = new SqlParameter("@CAT_TITLE", catalog.CatTitle);
        var catStringParam = new SqlParameter("@CAT_STRING", catalog.CatString);
        var changedWhoParam = new SqlParameter("@CHANGED_WHO", (object?)catalog.ChangedWho ?? DBNull.Value);

        var procName = procedure == CatalogUpdateProcedure.Save
            ? "PRTBMY_CATALOG_SAVE"
            : "PRTBMY_CATALOG_UPDATE";

        await _db.Database.ExecuteSqlRawAsync(
            $"EXEC dbo.{procName} @CAT_TITLE, @CAT_STRING, @CHANGED_WHO, @GUID OUTPUT",
            parameters: new[] { catTitleParam, catStringParam, changedWhoParam, guidParam },
            cancellationToken: cancellationToken);

        if (guidParam.Value == DBNull.Value)
        {
            return null;
        }

        var guid = (int)guidParam.Value;
        if (guid == 0)
        {
            return null;
        }

        return await _db.VwmyCatalogs.AsNoTracking().FirstOrDefaultAsync(x => x.Guid == guid, cancellationToken);
    }

    public async Task<VwmyCatalog?> UpdateAsync(int id, VwmyCatalog catalog, CancellationToken cancellationToken = default)
    {
        return await UpdateAsync(id, catalog, CatalogUpdateProcedure.Update, cancellationToken);
    }

    public async Task<bool> DeleteAsync(int id, CancellationToken cancellationToken = default)
    {
        var exists = await _db.VwmyCatalogs.AsNoTracking().AnyAsync(x => x.Guid == id, cancellationToken);
        if (!exists)
        {
            return false;
        }

        var guidParam = new SqlParameter("@GUID", id);
        await _db.Database.ExecuteSqlRawAsync(
            "EXEC dbo.PRTBMY_CATALOG_DELETE @GUID",
            parameters: new[] { guidParam },
            cancellationToken: cancellationToken);

        return true;
    }
}
