using MySqlConnector;

namespace Eis.Infrastructure.Data;

public sealed class DatabaseOptions
{
    private DatabaseOptions(string client, string? sqlitePath, string? mysqlConnectionString)
    {
        Client = client;
        SqlitePath = sqlitePath;
        MySqlConnectionString = mysqlConnectionString;
    }

    public string Client { get; }

    public string? SqlitePath { get; }

    public string? MySqlConnectionString { get; }

    public string TargetDescription => Client == "sqlite"
        ? SqlitePath ?? "SQLite"
        : $"MySQL database {MySqlDatabaseName}";

    public string? MySqlDatabaseName =>
        Client == "mysql" && MySqlConnectionString is { Length: > 0 } connectionString
            ? new MySqlConnectionStringBuilder(connectionString).Database
            : null;

    internal static DatabaseOptions CreateSqliteForTests(string path) =>
        new("sqlite", Path.GetFullPath(path), null);

    public static DatabaseOptions CreateSqlite(string path) =>
        new("sqlite", Path.GetFullPath(path), null);

    public static DatabaseOptions CreateMySqlFromEnvironment() =>
        new("mysql", null, BuildMySqlConnectionString());

    public static DatabaseOptions CreateMySql(string connectionString) =>
        new("mysql", null, new MySqlConnectionStringBuilder(connectionString).ConnectionString);

    public static DatabaseOptions FromEnvironment(string applicationRoot, bool production)
    {
        var client = (Environment.GetEnvironmentVariable("DATABASE_CLIENT")
            ?? (production ? "mysql" : "sqlite")).Trim().ToLowerInvariant();

        return client switch
        {
            "sqlite" => new DatabaseOptions(client, ResolveSqlitePath(applicationRoot), null),
            "mysql" => new DatabaseOptions(client, null, BuildMySqlConnectionString()),
            _ => throw new InvalidOperationException($"不支持的数据库类型：{client}")
        };
    }

    private static string ResolveSqlitePath(string applicationRoot)
    {
        var configured = Environment.GetEnvironmentVariable("SQLITE_PATH");
        var path = string.IsNullOrWhiteSpace(configured) ? Path.Combine("data", "exam.sqlite") : configured;
        return Path.GetFullPath(Path.IsPathRooted(path) ? path : Path.Combine(applicationRoot, path));
    }

    private static string BuildMySqlConnectionString()
    {
        var connectionUrl = Environment.GetEnvironmentVariable("DATABASE_URL");
        if (!string.IsNullOrWhiteSpace(connectionUrl))
        {
            return BuildFromUrl(connectionUrl);
        }

        var host = Environment.GetEnvironmentVariable("MYSQL_HOST");
        var user = Environment.GetEnvironmentVariable("MYSQL_USER");
        var database = Environment.GetEnvironmentVariable("MYSQL_DATABASE");
        if (string.IsNullOrWhiteSpace(host) || string.IsNullOrWhiteSpace(user) || string.IsNullOrWhiteSpace(database))
        {
            throw new InvalidOperationException("MySQL 配置不完整：请设置 DATABASE_URL，或 MYSQL_HOST、MYSQL_USER、MYSQL_DATABASE");
        }

        var builder = CreateMySqlBuilder();
        builder.Server = host;
        builder.Port = uint.TryParse(Environment.GetEnvironmentVariable("MYSQL_PORT"), out var port) ? port : 3306;
        builder.UserID = user;
        builder.Password = Environment.GetEnvironmentVariable("MYSQL_PASSWORD") ?? string.Empty;
        builder.Database = database;
        return builder.ConnectionString;
    }

    private static string BuildFromUrl(string connectionUrl)
    {
        if (!Uri.TryCreate(connectionUrl, UriKind.Absolute, out var uri) ||
            !uri.Scheme.Equals("mysql", StringComparison.OrdinalIgnoreCase))
        {
            throw new InvalidOperationException("DATABASE_URL 必须是有效的 mysql:// 地址");
        }

        var credentials = uri.UserInfo.Split(':', 2);
        var builder = CreateMySqlBuilder();
        builder.Server = uri.Host;
        builder.Port = uri.IsDefaultPort ? 3306u : checked((uint)uri.Port);
        builder.UserID = Uri.UnescapeDataString(credentials[0]);
        builder.Password = credentials.Length > 1 ? Uri.UnescapeDataString(credentials[1]) : string.Empty;
        builder.Database = Uri.UnescapeDataString(uri.AbsolutePath.Trim('/'));
        return builder.ConnectionString;
    }

    private static MySqlConnectionStringBuilder CreateMySqlBuilder() => new()
    {
        CharacterSet = "utf8mb4",
        ConnectionTimeout = 10,
        DefaultCommandTimeout = 30,
        Pooling = true,
        MaximumPoolSize = uint.TryParse(Environment.GetEnvironmentVariable("MYSQL_CONNECTION_LIMIT"), out var limit) ? limit : 10,
        ConnectionReset = true
    };
}
