using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
using Npgsql;
using QuantEngine.Infrastructure.Data;

namespace QuantEngine.Web.Pages.Admin.Database
{
    public class IndexModel : PageModel
    {
        private readonly IDbConnectionFactory _connectionFactory;
        private readonly ILogger<IndexModel> _logger;

        public List<string> TableList { get; set; } = new();
        public string? SelectedTable { get; set; }
        public List<string> ColumnNames { get; set; } = new();
        public List<Dictionary<string, object>> Rows { get; set; } = new();
        public string? PrimaryKeyColumn { get; set; }

        [BindProperty]
        public string? ActionTableName { get; set; }

        [BindProperty]
        public string? ActionRowKey { get; set; }

        public string? ErrorMessage { get; set; }
        public string? SuccessMessage { get; set; }

        public IndexModel(IDbConnectionFactory connectionFactory, ILogger<IndexModel> logger)
        {
            _connectionFactory = connectionFactory;
            _logger = logger;
        }

        public async Task OnGetAsync(string? tableName)
        {
            await LoadTableListAsync();

            if (!string.IsNullOrEmpty(tableName))
            {
                // Validate table name is in whitelist to prevent SQL Injection
                if (TableList.Contains(tableName, StringComparer.OrdinalIgnoreCase))
                {
                    SelectedTable = tableName;
                    await LoadTableDataAsync(tableName);
                }
                else
                {
                    ErrorMessage = "허용되지 않은 테이블명입니다.";
                }
            }
        }

        public async Task<IActionResult> OnPostSaveRowAsync()
        {
            await LoadTableListAsync();
            
            var tableName = Request.Form["tableName"].ToString();
            var pkColumn = Request.Form["pkColumn"].ToString();
            var pkValue = Request.Form["pkValue"].ToString();

            if (string.IsNullOrEmpty(tableName) || !TableList.Contains(tableName, StringComparer.OrdinalIgnoreCase))
            {
                ErrorMessage = "유효하지 않은 테이블입니다.";
                return Page();
            }

            try
            {
                using var conn = _connectionFactory.CreateConnection();
                if (conn.State != ConnectionState.Open) conn.Open();

                // Load target columns to update
                var columns = new List<string>();
                var parameters = new List<NpgsqlParameter>();
                
                foreach (var key in Request.Form.Keys)
                {
                    if (key == "tableName" || key == "pkColumn" || key == "pkValue" || key == "__RequestVerificationToken")
                        continue;

                    var val = Request.Form[key].ToString();
                    columns.Add($"\"{key}\" = @{key}");
                    
                    var param = new NpgsqlParameter($"@{key}", NpgsqlTypes.NpgsqlDbType.Text);
                    param.Value = (object?)val ?? DBNull.Value;
                    parameters.Add(param);
                }

                if (columns.Count > 0 && !string.IsNullOrEmpty(pkColumn))
                {
                    var sql = $"UPDATE {tableName} SET {string.Join(", ", columns)} WHERE \"{pkColumn}\" = @pk_val";
                    
                    using var cmd = new NpgsqlCommand(sql, (NpgsqlConnection)conn);
                    foreach (var p in parameters) cmd.Parameters.Add(p);
                    
                    var pkParam = new NpgsqlParameter("@pk_val", NpgsqlTypes.NpgsqlDbType.Text);
                    pkParam.Value = pkValue;
                    cmd.Parameters.Add(pkParam);

                    await cmd.ExecuteNonQueryAsync();
                    SuccessMessage = "행 데이터가 성공적으로 수정되었습니다.";
                }
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Failed to update row for {Table}", tableName);
                ErrorMessage = $"저장 실패: {ex.Message}";
            }

            return RedirectToPage(new { tableName });
        }

        public async Task<IActionResult> OnPostAddRowAsync()
        {
            await LoadTableListAsync();
            
            var tableName = Request.Form["tableName"].ToString();

            if (string.IsNullOrEmpty(tableName) || !TableList.Contains(tableName, StringComparer.OrdinalIgnoreCase))
            {
                ErrorMessage = "유효하지 않은 테이블입니다.";
                return Page();
            }

            try
            {
                using var conn = _connectionFactory.CreateConnection();
                if (conn.State != ConnectionState.Open) conn.Open();

                var colNames = new List<string>();
                var paramNames = new List<string>();
                var parameters = new List<NpgsqlParameter>();

                foreach (var key in Request.Form.Keys)
                {
                    if (key == "tableName" || key == "__RequestVerificationToken")
                        continue;

                    var val = Request.Form[key].ToString();
                    colNames.Add($"\"{key}\"");
                    paramNames.Add($"@{key}");

                    var param = new NpgsqlParameter($"@{key}", NpgsqlTypes.NpgsqlDbType.Text);
                    param.Value = (object?)val ?? DBNull.Value;
                    parameters.Add(param);
                }

                if (colNames.Count > 0)
                {
                    var sql = $"INSERT INTO {tableName} ({string.Join(", ", colNames)}) VALUES ({string.Join(", ", paramNames)})";
                    
                    using var cmd = new NpgsqlCommand(sql, (NpgsqlConnection)conn);
                    foreach (var p in parameters) cmd.Parameters.Add(p);

                    await cmd.ExecuteNonQueryAsync();
                    SuccessMessage = "새 데이터 행이 성공적으로 추가되었습니다.";
                }
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Failed to insert row for {Table}", tableName);
                ErrorMessage = $"추가 실패: {ex.Message}";
            }

            return RedirectToPage(new { tableName });
        }

        private async Task LoadTableListAsync()
        {
            TableList.Clear();
            try
            {
                using var conn = _connectionFactory.CreateConnection();
                if (conn.State != ConnectionState.Open) conn.Open();

                var sql = @"
                    SELECT table_schema || '.' || table_name AS full_name
                    FROM information_schema.tables 
                    WHERE table_schema IN ('quantengine', 'engine_history')
                      AND table_type = 'BASE TABLE'
                    ORDER BY table_schema, table_name;";

                using var cmd = new NpgsqlCommand(sql, (NpgsqlConnection)conn);
                using var reader = await cmd.ExecuteReaderAsync();
                while (await reader.ReadAsync())
                {
                    TableList.Add(reader.GetString(0));
                }
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Failed to load database table list.");
            }
        }

        private async Task LoadTableDataAsync(string tableName)
        {
            ColumnNames.Clear();
            Rows.Clear();
            PrimaryKeyColumn = null;

            try
            {
                using var conn = _connectionFactory.CreateConnection();
                if (conn.State != ConnectionState.Open) conn.Open();

                var parts = tableName.Split('.');
                var schema = parts[0];
                var tableOnly = parts[1];

                var pkSql = @"
                    SELECT a.attname
                    FROM pg_index i
                    JOIN pg_attribute a ON a.attrelid = i.indrelid AND a.attnum = ANY(i.indkey)
                    WHERE i.indrelid = @table_name::regclass
                      AND i.indisprimary;";

                using (var pkCmd = new NpgsqlCommand(pkSql, (NpgsqlConnection)conn))
                {
                    pkCmd.Parameters.AddWithValue("@table_name", tableName);
                    try
                    {
                        var pkResult = await pkCmd.ExecuteScalarAsync();
                        if (pkResult != null) PrimaryKeyColumn = pkResult.ToString();
                    }
                    catch { }
                }

                if (string.IsNullOrEmpty(PrimaryKeyColumn))
                {
                    if (tableName.Contains("settings")) PrimaryKeyColumn = "key";
                    else if (tableName.Contains("workspace_account")) PrimaryKeyColumn = "username";
                    else if (tableName.Contains("collection_runs")) PrimaryKeyColumn = "run_id";
                    else if (tableName.Contains("workspace_meta")) PrimaryKeyColumn = "key";
                }

                var dataSql = $"SELECT * FROM {tableName} LIMIT 100;";
                using var cmd = new NpgsqlCommand(dataSql, (NpgsqlConnection)conn);
                using var reader = await cmd.ExecuteReaderAsync();

                for (int i = 0; i < reader.FieldCount; i++)
                {
                    ColumnNames.Add(reader.GetName(i));
                }

                while (await reader.ReadAsync())
                {
                    var row = new Dictionary<string, object>();
                    for (int i = 0; i < reader.FieldCount; i++)
                    {
                        var val = reader.GetValue(i);
                        row[reader.GetName(i)] = val == DBNull.Value ? "null" : val;
                    }
                    Rows.Add(row);
                }
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Failed to load table data for {Table}", tableName);
                ErrorMessage = $"테이블 데이터 조회 실패: {ex.Message}";
            }
        }
    }
}
