/*
 * Copyright (c) 2026 Erik Darling, Darling Data LLC
 *
 * This file is part of the SQL Server Performance Monitor Lite.
 *
 * Licensed under the MIT License. See LICENSE file in the project root for full license information.
 */

using System;
using System.ComponentModel;
using System.Text.Json.Serialization;
using Microsoft.Data.SqlClient;
using PerformanceMonitorLite.Services;

namespace PerformanceMonitorLite.Models;

public class ServerConnection : INotifyPropertyChanged
{
    public string Id { get; set; } = Guid.NewGuid().ToString();
    public string ServerName { get; set; } = string.Empty;
    public string DisplayName { get; set; } = string.Empty;
    
    /// <summary>
    /// Backward compatibility property for old servers.json files.
    /// Returns true if authentication type is Windows.
    /// Setter updates AuthenticationType for migration from old configs.
    /// </summary>
    public bool UseWindowsAuth 
    { 
        get => AuthenticationType == AuthenticationTypes.Windows;
        set 
        {
            // During JSON deserialization of old configs, update AuthenticationType based on UseWindowsAuth
            // Only apply this if AuthenticationType is still at default (indicating old JSON without that field)
            if (AuthenticationType == AuthenticationTypes.Windows && !value)
            {
                // Old config with UseWindowsAuth=false -> SQL Server auth
                AuthenticationType = AuthenticationTypes.SqlServer;
            }
            // If value is true, keep Windows (already the default)
        }
    }
    
    /// <summary>
    /// Authentication type: Windows, SqlServer, or EntraMFA
    /// </summary>
    public string AuthenticationType { get; set; } = AuthenticationTypes.Windows;
    
    public string? Description { get; set; }
    public DateTime CreatedDate { get; set; } = DateTime.Now;
    public DateTime LastConnected { get; set; } = DateTime.Now;
    public bool IsFavorite { get; set; }
    public bool IsEnabled { get; set; } = true;

    /// <summary>
    /// Encryption mode for the connection. Valid values: Optional, Mandatory, Strict.
    /// Default is Mandatory for security. Users can opt down to Optional if needed.
    /// </summary>
    public string EncryptMode { get; set; } = "Mandatory";

    /// <summary>
    /// Whether to trust the server certificate without validation.
    /// Default is false for security. Enable for servers with self-signed certificates.
    /// </summary>
    public bool TrustServerCertificate { get; set; } = false;

    /// <summary>
    /// Monthly cost of this server in USD, used for FinOps cost attribution.
    /// Set to 0 to hide cost columns. All FinOps costs are proportional to this budget.
    /// </summary>
    public decimal MonthlyCostUsd { get; set; } = 0m;

    /// <summary>
    /// Optional database name for the initial connection.
    /// Required for Azure SQL Database (which doesn't allow connecting to master).
    /// Leave empty for on-premises SQL Server (defaults to master).
    /// </summary>
    public string? DatabaseName { get; set; }

    /// <summary>
    /// Optional database where community stored procedures (sp_IndexCleanup) are installed.
    /// When null or empty, falls back to the connection database.
    /// </summary>
    public string? UtilityDatabase { get; set; }

    /// <summary>
    /// When true, sets ApplicationIntent=ReadOnly on the connection string.
    /// Required for connecting to AG listener read-only replicas and
    /// Azure SQL Business Critical / Managed Instance built-in read replicas.
    /// </summary>
    public bool ReadOnlyIntent { get; set; } = false;

    /// <summary>
    /// When true, sets MultiSubnetFailover=true on the connection string.
    /// Recommended for AG listeners and FCIs spanning multiple subnets.
    /// </summary>
    public bool MultiSubnetFailover { get; set; } = false;

    /// <summary>
    /// User databases to skip in per-database collectors (query_store, file_io_stats, etc.).
    /// System databases (master/tempdb/model/msdb) and the connection database itself are always
    /// excluded by the collectors and aren't represented here.
    /// </summary>
    public System.Collections.Generic.List<string> ExcludedDatabases { get; set; } = new();

    /// <summary>
    /// Server name with "(Read-Only)" suffix when ReadOnlyIntent is enabled.
    /// Used for sidebar subtitle and status text.
    /// </summary>
    [JsonIgnore]
    public string ServerNameDisplay => ReadOnlyIntent ? $"{ServerName} (Read-Only)" : ServerName;

    /// <summary>
    /// Display name with "(Read-Only)" suffix when ReadOnlyIntent is enabled.
    /// Used for alerts, tray notifications, status bar, and overview cards.
    /// </summary>
    [JsonIgnore]
    public string DisplayNameWithIntent => ReadOnlyIntent ? $"{DisplayName} (Read-Only)" : DisplayName;

    /// <summary>
    /// Display-only property for showing authentication type in UI.
    /// </summary>
    [JsonIgnore]
    public string AuthenticationDisplay => AuthenticationType switch
    {
        AuthenticationTypes.EntraMFA => "Microsoft Entra MFA",
        AuthenticationTypes.SqlServer => "SQL Server",
        _ => "Windows"
    };

    /// <summary>
    /// Actual connection status from the most recent connection check.
    /// null = not checked yet, true = online, false = offline.
    /// </summary>
    [JsonIgnore]
    public bool? IsOnline { get; set; }

    /// <summary>
    /// Whether one or more collectors are currently failing for this server.
    /// null = not yet determined; true = some collectors have consecutive errors; false = all healthy.
    /// </summary>
    [JsonIgnore]
    public bool? HasCollectorErrors { get; set; }

    /// <summary>
    /// Computed dot status for the sidebar indicator. One of: "Unknown", "Online", "Warning", "Offline".
    /// Drives the Ellipse fill via DataTrigger in MainWindow.xaml.
    /// </summary>
    [JsonIgnore]
    public string DotStatus
    {
        get
        {
            if (IsOnline == true)
                return HasCollectorErrors == true ? "Warning" : "Online";
            if (IsOnline == false)
                return "Offline";
            return "Unknown"; // null — not yet checked
        }
    }

    /// <summary>
    /// Display-only property for showing status in UI.
    /// </summary>
    [JsonIgnore]
    public string StatusDisplay => IsEnabled ? "Enabled" : "Disabled";

    private string? _installedVersion;

    /// <summary>
    /// PerformanceMonitor Lite app version monitoring this server (e.g., "2.10.0").
    /// Populated by the Manage Servers window. Not persisted — runtime-only display field.
    /// Same value for every row since one Lite process monitors all servers.
    /// </summary>
    [JsonIgnore]
    public string? InstalledVersion
    {
        get => _installedVersion;
        set
        {
            if (_installedVersion == value) return;
            _installedVersion = value;
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(InstalledVersion)));
        }
    }

    public event PropertyChangedEventHandler? PropertyChanged;

    /// <summary>
    /// Builds and returns a connection string for this server.
    /// Credentials are retrieved from Windows Credential Manager if SQL auth is used.
    /// </summary>
    public string GetConnectionString(CredentialService credentialService)
    {
        string? username = null;
        string? password = null;

        if (AuthenticationType == AuthenticationTypes.SqlServer)
        {
            var cred = credentialService.GetCredential(Id);
            if (cred.HasValue)
            {
                username = cred.Value.Username;
                password = cred.Value.Password;
            }
        }

        return BuildConnectionString(username, password);
    }

    /// <summary>
    /// Returns a connection string targeting UtilityDatabase if set, otherwise falls back to GetConnectionString().
    /// Used for locating community stored procedures (sp_IndexCleanup) that may be installed in a non-default database.
    /// </summary>
    public string GetUtilityConnectionString(CredentialService credentialService)
    {
        var baseConnStr = GetConnectionString(credentialService);

        if (string.IsNullOrWhiteSpace(UtilityDatabase))
            return baseConnStr;

        var builder = new SqlConnectionStringBuilder(baseConnStr)
        {
            InitialCatalog = UtilityDatabase
        };
        return builder.ConnectionString;
    }

    /// <summary>
    /// Builds the connection string with the given credentials.
    /// </summary>
    private string BuildConnectionString(string? username, string? password)
    {
        var builder = new SqlConnectionStringBuilder
        {
            DataSource = ServerName,
            InitialCatalog = string.IsNullOrWhiteSpace(DatabaseName) ? "master" : DatabaseName,
            ApplicationName = "PerformanceMonitorLite",
            ConnectTimeout = 15,
            CommandTimeout = 60,
            TrustServerCertificate = TrustServerCertificate,
            MultipleActiveResultSets = true,
            ApplicationIntent = ReadOnlyIntent ? ApplicationIntent.ReadOnly : ApplicationIntent.ReadWrite,
            MultiSubnetFailover = MultiSubnetFailover
        };

        // Set encryption mode
        builder.Encrypt = EncryptMode switch
        {
            "Mandatory" => SqlConnectionEncryptOption.Mandatory,
            "Strict" => SqlConnectionEncryptOption.Strict,
            _ => SqlConnectionEncryptOption.Optional
        };

        if (AuthenticationType == AuthenticationTypes.Windows)
        {
            builder.IntegratedSecurity = true;
        }
        else if (AuthenticationType == AuthenticationTypes.SqlServer)
        {
            builder.IntegratedSecurity = false;
            builder.UserID = username ?? string.Empty;
            builder.Password = password ?? string.Empty;
        }
        else if (AuthenticationType == AuthenticationTypes.EntraMFA)
        {
            // Microsoft Entra MFA (Azure AD Interactive)
            builder.IntegratedSecurity = false;
            builder.Authentication = SqlAuthenticationMethod.ActiveDirectoryInteractive;
            // Optionally set UserID (email/UPN)
            if (!string.IsNullOrWhiteSpace(username))
            {
                builder.UserID = username;
            }
        }

        return builder.ConnectionString;
    }

    /// <summary>
    /// Checks if credentials are stored in Windows Credential Manager for this server.
    /// </summary>
    public bool HasStoredCredentials(CredentialService credentialService)
    {
        if (AuthenticationType == AuthenticationTypes.Windows || AuthenticationType == AuthenticationTypes.EntraMFA)
        {
            return true;
        }

        return credentialService.CredentialExists(Id);
    }
}
