using Journal.Core.Models;

namespace Journal.Core.Services.Config;

public sealed class JournalConfigService : IJournalConfigService
{
    public JournalConfig Current => BuildConfig();

    private static JournalConfig BuildConfig()
    {
        var projectRoot = ResolveProjectRoot();
        var appDirectory = ResolvePath("JOURNAL_APP_DIR", Path.Combine(projectRoot, "journal"));

        var vaultDirectory = ResolvePath("JOURNAL_VAULT_DIR", Path.Combine(appDirectory, "vault"));
        var logDirectory = ResolvePath("JOURNAL_LOG_DIR", Path.Combine(projectRoot, "logs"));

        var pidFile = ResolvePath("JOURNAL_PID_FILE", Path.Combine(logDirectory, "nicegui_server.pid"));
        var serverControlFile = ResolvePath("JOURNAL_SERVER_CONTROL_FILE", Path.Combine(logDirectory, "server_control.action"));

        var nlpBackend = (Environment.GetEnvironmentVariable("JOURNAL_NLP_BACKEND") ?? "auto").Trim().ToLowerInvariant();
        if (nlpBackend is not ("auto" or "spacy" or "fallback"))
            nlpBackend = "auto";

        var aiProvider = (Environment.GetEnvironmentVariable("JOURNAL_AI_PROVIDER") ?? "llamasharp").Trim().ToLowerInvariant();
        if (aiProvider is not ("none" or "python-sidecar" or "llamasharp"))
            aiProvider = "none";

        var pythonExecutable = Environment.GetEnvironmentVariable("JOURNAL_PYTHON_EXE");
        if (string.IsNullOrWhiteSpace(pythonExecutable))
            pythonExecutable = "python";

        var defaultAiSidecarPath = Path.Combine(projectRoot, "journal", "ai", "sidecar.py");
        var pythonAiSidecarPath = ResolvePath("JOURNAL_AI_SIDECAR_PATH", defaultAiSidecarPath);
        var aiSidecarTimeoutMs = ParseInt("JOURNAL_AI_TIMEOUT_MS", 45000);

        return new JournalConfig(
            ProjectRoot: projectRoot,
            AppDirectory: appDirectory,
            VaultDirectory: vaultDirectory,
            LogDirectory: logDirectory,
            PidFile: pidFile,
            ServerControlFile: serverControlFile,
            DatabaseFilename: Environment.GetEnvironmentVariable("JOURNAL_DATABASE_FILENAME") ?? "journal_cache.db",
            CloudAiApiKey: Environment.GetEnvironmentVariable("CLOUDAI_API_KEY") ?? "",
            CloudAiApiUrl: Environment.GetEnvironmentVariable("CLOUDAI_API_URL") ?? "",
            LlamaCppUrl: Environment.GetEnvironmentVariable("LLAMA_CPP_URL") ?? "http://127.0.0.1:8085/v1/completions",
            LlamaCppModel: Environment.GetEnvironmentVariable("LLAMA_CPP_MODEL") ?? "qwen/qwen3-4b",
            LlamaCppTimeout: ParseInt("LLAMA_CPP_TIMEOUT", 6000),
            EmbeddingApiUrl: Environment.GetEnvironmentVariable("EMBEDDING_API_URL") ?? "http://127.0.0.1:8086/v1/embeddings",
            EmbeddingModelName: Environment.GetEnvironmentVariable("EMBEDDING_MODEL_NAME") ?? "text-embedding-nomic-embed-text-v2-moe",
            ModelContextTokens: ParseInt("MODEL_CONTEXT_TOKENS", 131072),
            ChunkTokenBudget: ParseInt("CHUNK_TOKEN_BUDGET", 120000),
            MicrophoneDeviceIndex: ParseNullableInt("MICROPHONE_DEVICE_INDEX"),
            SpeechRecognitionEngine: Environment.GetEnvironmentVariable("SPEECH_RECOGNITION_ENGINE") ?? "whisper",
            WhisperModelSize: Environment.GetEnvironmentVariable("WHISPER_MODEL_SIZE") ?? "base",
            NlpBackend: nlpBackend,
            AiProvider: aiProvider,
            PythonExecutable: pythonExecutable,
            PythonAiSidecarPath: pythonAiSidecarPath,
            AiSidecarTimeoutMs: aiSidecarTimeoutMs,
            GgufModelPath: ResolveGgufModelPath(projectRoot));
    }

    private static string ResolveProjectRoot()
    {
        var envRoot = Environment.GetEnvironmentVariable("JOURNAL_PROJECT_ROOT");
        if (!string.IsNullOrWhiteSpace(envRoot))
            return Path.GetFullPath(envRoot);

        var cwd = Directory.GetCurrentDirectory();
        if (Directory.Exists(Path.Combine(cwd, "journal")))
            return Path.GetFullPath(cwd);

        var upOne = Path.GetFullPath(Path.Combine(cwd, ".."));
        if (Directory.Exists(Path.Combine(upOne, "journal")))
            return upOne;

        var upTwo = Path.GetFullPath(Path.Combine(cwd, "..", ".."));
        if (Directory.Exists(Path.Combine(upTwo, "journal")))
            return upTwo;

        return Path.GetFullPath(cwd);
    }

    private static string ResolvePath(string envVar, string defaultPath)
    {
        var value = Environment.GetEnvironmentVariable(envVar);
        var raw = string.IsNullOrWhiteSpace(value) ? defaultPath : value;
        return Path.GetFullPath(raw);
    }

    private static int ParseInt(string envVar, int defaultValue)
    {
        var value = Environment.GetEnvironmentVariable(envVar);
        return int.TryParse(value, out var parsed) ? parsed : defaultValue;
    }

    private static int? ParseNullableInt(string envVar)
    {
        var value = Environment.GetEnvironmentVariable(envVar);
        if (string.IsNullOrWhiteSpace(value))
            return null;
        return int.TryParse(value, out var parsed) ? parsed : null;
    }

    private static string ResolveGgufModelPath(string projectRoot)
    {
        var fromEnv = Environment.GetEnvironmentVariable("JOURNAL_GGUF_MODEL_PATH");
        if (!string.IsNullOrWhiteSpace(fromEnv))
            return Path.GetFullPath(fromEnv);

        var modelsDir = Path.Combine(projectRoot, "models");
        if (Directory.Exists(modelsDir))
        {
            var first = Directory.EnumerateFiles(modelsDir, "*.gguf").FirstOrDefault();
            if (first is not null)
                return Path.GetFullPath(first);
        }

        return Path.Combine(modelsDir, "model.gguf");
    }
}

