using Core.Entities{{ GroupNamespace }};
using Core.Interfaces{{ GroupNamespace }};
using Dapper;
using Npgsql;
using System.Collections.Generic;
using System.Threading.Tasks;
using Infrastructure.Data.Common;

namespace Infrastructure.Data{{ GroupNamespace }}
{
    public class {{ TableNamePascal }}Repository : I{{ TableNamePascal }}Repository
    {
        private readonly string _connectionString;

        public {{ TableNamePascal }}Repository(string connectionString)
        {
            _connectionString = connectionString;
        }

        public async Task<{{ TableNamePascal }}?> GetByIdAsync({{ PrimaryKey.CSharpType }} id)
        {
            using var connection = new NpgsqlConnection(_connectionString);
            return await connection.QueryFirstOrDefaultAsync<{{ TableNamePascal }}>(
                $"SELECT * FROM {{ TableNameSnake }} WHERE {{ PrimaryKey.NameSnake }} = @id", 
                new { id });
        }

        public async Task<IEnumerable<{{ TableNamePascal }}>> GetAllAsync()
        {
            using var connection = new NpgsqlConnection(_connectionString);
            return await connection.QueryAsync<{{ TableNamePascal }}>(
                $"SELECT * FROM {{ TableNameSnake }}");
        }

        public async Task<(IEnumerable<{{ TableNamePascal }}> Items, int TotalCount)> GetPagedAsync(
            int pageNumber, 
            int pageSize, 
            string? searchTerm = null,
            string? sortBy = null,
            bool sortDescending = false,
            Dictionary<string, object>? filters = null)
        {
            using var connection = new NpgsqlConnection(_connectionString);
            var allowedColumns = new[] { "{{ PrimaryKey.NameSnake }}"{% for col in Columns %}{% if not col.IsPrimaryKey %}, "{{ col.NameSnake }}"{% endif %}{% endfor %} };

            var builder = SqlQueryBuilder
                .From("{{ TableNameSnake }}", allowedColumns)
                .SearchAcross(new string[] { {% for col in Columns %}{% if col.CSharpType == 'string' %}"{{ col.NameSnake }}"{% if not loop.last %}, {% endif %}{% endif %}{% endfor %} }, searchTerm)
                .ApplyFilters(filters)
                .Paginate(pageNumber, pageSize);

            var allowedSet = new HashSet<string>(allowedColumns, System.StringComparer.OrdinalIgnoreCase);
            var orderBy = !string.IsNullOrWhiteSpace(sortBy) && allowedSet.Contains(sortBy!) ? sortBy! : "{{ PrimaryKey.NameSnake }}";
            builder.OrderBy(orderBy, sortDescending);

            var (dataSql, parameters) = builder.BuildSelect();
            var (countSql, _) = builder.BuildCount();

            using var multi = await connection.QueryMultipleAsync(dataSql + ";" + countSql, parameters);
            var items = await multi.ReadAsync<{{ TableNamePascal }}>();
            var totalCount = await multi.ReadSingleAsync<int>();
            return (items, totalCount);
        }

        public async Task<{{ TableNamePascal }}> AddAsync({{ TableNamePascal }} entity)
        {
            using var connection = new NpgsqlConnection(_connectionString);
            var sql = @"INSERT INTO {{ TableNameSnake }} 
                ({% for col in NonPrimaryColumns %}{{ col.NameSnake }}{% if not loop.last %}, {% endif %}{% endfor %})
                VALUES
                ({% for col in NonPrimaryColumns %}{% if col.NameSnake == 'created_at' or col.NameSnake == 'updated_at' %}NOW(){% else %}@{{ col.NamePascal }}{% endif %}{% if not loop.last %}, {% endif %}{% endfor %})
                RETURNING *";
            
            var createdEntity = await connection.QuerySingleAsync<{{ TableNamePascal }}>(sql, entity);
            return createdEntity;
        }

        public async Task UpdateAsync({{ TableNamePascal }} entity)
        {
            using var connection = new NpgsqlConnection(_connectionString);
            var sql = @"UPDATE {{ TableNameSnake }} SET
                {% set ns = namespace(items=[], has_updated_at=false) %}
                {% for col in NonPrimaryColumns %}
                {% if col.NameSnake == 'updated_at' %}
                {% set ns.has_updated_at = true %}
                {% elif col.NameSnake != 'created_at' %}
                {% set _ = ns.items.append(col.NameSnake ~ ' = @' ~ col.NamePascal) %}
                {% endif %}
                {% endfor %}
                {% if ns.has_updated_at %}{% set _ = ns.items.append('updated_at = NOW()') %}{% endif %}
                {{ ns.items | join(',\n                ') }}
                WHERE {{ PrimaryKey.NameSnake }} = @{{ PrimaryKey.NamePascal }}";
            await connection.ExecuteAsync(sql, entity);
        }

        public async Task DeleteAsync({{ PrimaryKey.CSharpType }} id)
        {
            using var connection = new NpgsqlConnection(_connectionString);
            await connection.ExecuteAsync(
                $"DELETE FROM {{ TableNameSnake }} WHERE {{ PrimaryKey.NameSnake }} = @id", 
                new { id });
        }
    }
}