using Eis.Infrastructure.Configuration;
using Eis.Infrastructure.Data;

namespace Eis.Tools;

internal static class Program
{
    public static async Task<int> Main(string[] args)
    {
        try
        {
            var command = DatabaseToolCommand.Parse(args);
            if (command.ShowHelp)
            {
                Console.WriteLine(DatabaseToolCommand.Usage);
                return 0;
            }

            var applicationRoot = command.ApplicationRoot ?? FindApplicationRoot();
            EnvironmentFile.Load(applicationRoot);
            var options = BuildDatabaseOptions(command, applicationRoot);
            if (command.Force && options.Client != "mysql")
            {
                throw new DatabaseToolUsageException("--force 只能用于 MySQL 测试数据库。");
            }
            var confirmedTarget = NormalizeConfirmation(command.ConfirmedTarget, options, applicationRoot);

            Console.WriteLine($"命令：database {command.Mode.ToString().ToLowerInvariant()}");
            Console.WriteLine($"目标：{options.TargetDescription}");
            if (command.DryRun)
            {
                Console.WriteLine("模式：只预检，不写入数据库");
            }

            var service = new DatabaseMaintenanceService(options);
            var result = await service.ExecuteAsync(
                command.Mode,
                confirmedTarget,
                command.Force,
                command.DryRun,
                CancellationToken.None);
            PrintResult(result);
            return 0;
        }
        catch (DatabaseToolUsageException exception)
        {
            Console.Error.WriteLine(exception.Message);
            Console.Error.WriteLine();
            Console.Error.WriteLine(DatabaseToolCommand.Usage);
            return 2;
        }
        catch (Exception exception)
        {
            Console.Error.WriteLine($"数据库工具执行失败：{exception.Message}");
            return 1;
        }
    }

    private static DatabaseOptions BuildDatabaseOptions(
        DatabaseToolCommand command,
        string applicationRoot)
    {
        var client = command.Client ??
                     Environment.GetEnvironmentVariable("DATABASE_CLIENT")?.Trim().ToLowerInvariant() ??
                     "sqlite";
        return client switch
        {
            "sqlite" => DatabaseOptions.CreateSqlite(ResolveSqlitePath(command.SqlitePath, applicationRoot)),
            "mysql" when command.SqlitePath is not null =>
                throw new DatabaseToolUsageException("--path 只能和 --sqlite 一起使用"),
            "mysql" => DatabaseOptions.CreateMySqlFromEnvironment(),
            _ => throw new DatabaseToolUsageException($"不支持的数据库类型：{client}")
        };
    }

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

    private static string? NormalizeConfirmation(
        string? confirmation,
        DatabaseOptions options,
        string applicationRoot)
    {
        if (confirmation is null || options.Client != "sqlite")
        {
            return confirmation;
        }
        return Path.GetFullPath(Path.IsPathRooted(confirmation)
            ? confirmation
            : Path.Combine(applicationRoot, confirmation));
    }

    private static string FindApplicationRoot()
    {
        foreach (var start in new[] { Directory.GetCurrentDirectory(), AppContext.BaseDirectory })
        {
            var directory = new DirectoryInfo(Path.GetFullPath(start));
            while (directory is not null)
            {
                if (File.Exists(Path.Combine(directory.FullName, "Eis.slnx")) ||
                    File.Exists(Path.Combine(directory.FullName, ".env")))
                {
                    return directory.FullName;
                }
                directory = directory.Parent;
            }
        }
        return Path.GetFullPath(Directory.GetCurrentDirectory());
    }

    private static void PrintResult(DatabaseMaintenanceResult result)
    {
        if (result.DryRun)
        {
            Console.WriteLine("预检完成：未修改数据库。");
        }
        else
        {
            Console.WriteLine(result.Mode switch
            {
                DatabaseMaintenanceMode.Init => "数据库结构和基础配置初始化完成。",
                DatabaseMaintenanceMode.Reset => "数据库已重建为空业务系统。",
                _ => "演示数据导入完成。"
            });
        }

        if (result.BackupPath is not null)
        {
            Console.WriteLine($"原数据库备份：{result.BackupPath}");
        }
        if (result.Summary is { } summary)
        {
            Console.WriteLine(
                $"演示数据：学校 {summary.Schools}，用户 {summary.Users}，考生 {summary.Candidates}，" +
                $"报名 {summary.Registrations}，成绩 {summary.Results}，招生记录 {summary.AdmissionRecords}。");
            Console.WriteLine("预置测试账号密码：12345678");
        }
    }
}

internal sealed record DatabaseToolCommand(
    DatabaseMaintenanceMode Mode,
    string? Client,
    string? SqlitePath,
    string? ConfirmedTarget,
    string? ApplicationRoot,
    bool Force,
    bool DryRun,
    bool ShowHelp)
{
    public const string Usage =
        """
        用法：
          dotnet Eis.Tools.dll database init  [--sqlite|--mysql] [--path <sqlite文件>] [--dry-run]
          dotnet Eis.Tools.dll database reset [--sqlite|--mysql] [--path <sqlite文件>] [--confirm-target <目标>] [--force] [--dry-run]
          dotnet Eis.Tools.dll database seed  [--sqlite|--mysql] [--path <sqlite文件>] [--confirm-target <目标>] [--force] [--dry-run]

        说明：
          init   非破坏性建表和基础配置初始化。
          reset  重建为空业务库；SQLite 自动备份原文件。
          seed   导入内置的 1200 名考生演示数据；SQLite 自动备份原文件。

          --confirm-target 对 SQLite 填写最终数据库路径，对 MySQL 填写数据库名。
          --force          仅在确认 MySQL 中已有非演示业务数据可覆盖时使用。
          --root           指定 .env 和相对 SQLite 路径的应用根目录。
        """;

    public static DatabaseToolCommand Parse(string[] args)
    {
        if (args.Length == 0 || args.Any(item => item is "-h" or "--help"))
        {
            return new(
                DatabaseMaintenanceMode.Init,
                null,
                null,
                null,
                null,
                false,
                false,
                true);
        }
        if (args.Length < 2 || !args[0].Equals("database", StringComparison.OrdinalIgnoreCase))
        {
            throw new DatabaseToolUsageException("第一个参数必须是 database。");
        }

        var mode = args[1].ToLowerInvariant() switch
        {
            "init" => DatabaseMaintenanceMode.Init,
            "reset" => DatabaseMaintenanceMode.Reset,
            "seed" => DatabaseMaintenanceMode.Seed,
            _ => throw new DatabaseToolUsageException($"不支持的 database 命令：{args[1]}")
        };
        string? client = null;
        string? path = null;
        string? confirmation = null;
        string? root = null;
        var force = false;
        var dryRun = false;
        for (var index = 2; index < args.Length; index++)
        {
            switch (args[index])
            {
                case "--sqlite":
                    SetClient(ref client, "sqlite");
                    break;
                case "--mysql":
                    SetClient(ref client, "mysql");
                    break;
                case "--path":
                    path = NextValue(args, ref index, "--path");
                    break;
                case "--confirm-target":
                    confirmation = NextValue(args, ref index, "--confirm-target");
                    break;
                case "--root":
                    root = Path.GetFullPath(NextValue(args, ref index, "--root"));
                    break;
                case "--force":
                    force = true;
                    break;
                case "--dry-run":
                    dryRun = true;
                    break;
                default:
                    throw new DatabaseToolUsageException($"无法识别的参数：{args[index]}");
            }
        }

        if (mode == DatabaseMaintenanceMode.Init && force)
        {
            throw new DatabaseToolUsageException("database init 不接受 --force。");
        }
        return new(mode, client, path, confirmation, root, force, dryRun, false);
    }

    private static void SetClient(ref string? current, string value)
    {
        if (current is not null && current != value)
        {
            throw new DatabaseToolUsageException("--sqlite 和 --mysql 不能同时使用。");
        }
        current = value;
    }

    private static string NextValue(string[] args, ref int index, string option)
    {
        if (++index >= args.Length || args[index].StartsWith("--", StringComparison.Ordinal))
        {
            throw new DatabaseToolUsageException($"{option} 缺少参数值。");
        }
        return args[index];
    }
}

internal sealed class DatabaseToolUsageException(string message) : Exception(message);
