import { createHash } from "node:crypto";
import { execFileSync } from "node:child_process";
import { existsSync, readFileSync, statSync } from "node:fs";
import { homedir } from "node:os";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import type { MemoryNodeConfig, ProjectResolution } from "./types.js";

interface ResolveProjectOptions {
  projectPath?: string;
  inputProjectId?: string;
}

function readPackageVersion(): string {
  try {
    const currentDir = dirname(fileURLToPath(import.meta.url));
    const packageJsonPath = resolve(currentDir, "..", "package.json");
    const packageJsonContent = readFileSync(packageJsonPath, "utf8");
    const packageJson = JSON.parse(packageJsonContent) as { version?: unknown };
    if (typeof packageJson.version === "string" && packageJson.version.length > 0) {
      return packageJson.version;
    }
  } catch {
    // ignore and fallback
  }
  return "0.1.0";
}

function parsePositiveInt(raw: string | undefined, fallback: number): number {
  const parsed = Number.parseInt(raw ?? "", 10);
  return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
}

function parseBooleanFlag(raw: string | undefined, fallback: boolean): boolean {
  if (raw === undefined) return fallback;
  const normalized = raw.trim().toLowerCase();
  if (normalized === "1" || normalized === "true" || normalized === "yes" || normalized === "on") return true;
  if (normalized === "0" || normalized === "false" || normalized === "no" || normalized === "off") return false;
  return fallback;
}

function parseListEnv(raw: string | undefined): string[] | undefined {
  if (typeof raw !== "string") return undefined;
  const values = raw
    .split(/[;,\n]/u)
    .map((entry) => entry.trim())
    .filter(Boolean);
  return values.length > 0 ? values : undefined;
}

function uniqueCaseAware(values: string[]): string[] {
  const seen = new Set<string>();
  const normalized: string[] = [];
  for (const value of values) {
    const key = process.platform === "win32" ? value.toLowerCase() : value;
    if (seen.has(key)) continue;
    seen.add(key);
    normalized.push(value);
  }
  return normalized;
}

function parseEnvFile(envPath: string): Record<string, string> {
  const map: Record<string, string> = {};
  if (!existsSync(envPath)) {
    return map;
  }

  const content = readFileSync(envPath, "utf8");
  for (const line of content.split(/\r?\n/u)) {
    const trimmed = line.trim();
    if (!trimmed || trimmed.startsWith("#")) {
      continue;
    }
    const idx = trimmed.indexOf("=");
    if (idx <= 0) {
      continue;
    }
    const key = trimmed.slice(0, idx).trim();
    let value = trimmed.slice(idx + 1).trim();
    if ((value.startsWith("\"") && value.endsWith("\"")) || (value.startsWith("'") && value.endsWith("'"))) {
      value = value.slice(1, -1);
    }
    map[key] = value;
  }

  return map;
}

function canonicalizeGitRemote(remote: string): string {
  const cleaned = remote.trim().replace(/\.git$/u, "").toLowerCase();
  const scpLike = cleaned.match(/^git@([^:]+):(.+)$/u);
  if (scpLike) {
    return `${scpLike[1]}/${scpLike[2]}`;
  }
  try {
    const parsed = new URL(cleaned);
    const path = parsed.pathname.replace(/^\//u, "");
    return `${parsed.hostname}/${path}`;
  } catch {
    return cleaned.replace(/[:]+/gu, "/");
  }
}

function readGitRemote(projectPath: string): string | undefined {
  try {
    const out = execFileSync("git", ["-C", projectPath, "config", "--get", "remote.origin.url"], {
      encoding: "utf8",
      stdio: ["ignore", "pipe", "ignore"]
    });
    const remote = out.trim();
    return remote.length > 0 ? canonicalizeGitRemote(remote) : undefined;
  } catch {
    return undefined;
  }
}

export function hashProjectPath(projectPath: string): string {
  const normalized = resolve(projectPath).toLowerCase();
  return createHash("sha256").update(normalized).digest("hex").slice(0, 24);
}

export const DEFAULT_CONFIG: MemoryNodeConfig = {
  serverName: "memory-node",
  serverVersion: readPackageVersion(),
  dbPath: process.env.MEMORY_DB_PATH?.trim() || resolve(homedir(), ".mcp-servers", "memory", "memory.sqlite"),
  maxEntryBytes: parsePositiveInt(process.env.MEMORY_MAX_ENTRY_BYTES, 131072),
  maxSearchResults: parsePositiveInt(process.env.MEMORY_MAX_SEARCH_RESULTS, 25),
  auditLogEnabled: parseBooleanFlag(process.env.MEMORY_AUDIT_LOG, true),
  inputLimits: {
    maxTags: parsePositiveInt(process.env.MEMORY_MAX_TAGS, 20),
    maxTitleChars: parsePositiveInt(process.env.MEMORY_MAX_TITLE_CHARS, 200),
    maxSummaryChars: parsePositiveInt(process.env.MEMORY_MAX_SUMMARY_CHARS, 500),
    maxSourceRefChars: parsePositiveInt(process.env.MEMORY_MAX_SOURCE_REF_CHARS, 400),
    maxSourceUriChars: parsePositiveInt(process.env.MEMORY_MAX_SOURCE_URI_CHARS, 1000),
    maxProjectIdChars: parsePositiveInt(process.env.MEMORY_MAX_PROJECT_ID_CHARS, 200),
    maxScopeChars: parsePositiveInt(process.env.MEMORY_MAX_SCOPE_CHARS, 120),
    maxTopicChars: parsePositiveInt(process.env.MEMORY_MAX_TOPIC_CHARS, 200),
    maxTagChars: parsePositiveInt(process.env.MEMORY_MAX_TAG_CHARS, 80)
  },
  featureFlags: {
    importExport: process.env.MEMORY_FEATURE_IMPORT_EXPORT === "1",
    resources: process.env.MEMORY_FEATURE_RESOURCES === "1",
    prompts: process.env.MEMORY_FEATURE_PROMPTS === "1",
    vectorSearch: process.env.MEMORY_FEATURE_VECTOR === "1"
  }
};

export function resolveAllowedRootsForProject(projectPath?: string): string[] {
  let targetRootsRaw: string | undefined;
  if (projectPath) {
    const targetEnv = parseEnvFile(resolve(projectPath, ".env"));
    targetRootsRaw = targetEnv.MEMORY_ALLOWED_ROOTS;
  }

  const launcherRootsRaw = process.env.MEMORY_ALLOWED_ROOTS;
  const parsedTarget = parseListEnv(targetRootsRaw);
  const parsedLauncher = parseListEnv(launcherRootsRaw);

  const selected = parsedTarget ?? parsedLauncher ?? (projectPath ? [projectPath] : []);
  const baseForRelative = parsedTarget ? projectPath ?? process.cwd() : process.cwd();

  return uniqueCaseAware(selected.map((entry) => resolve(baseForRelative, entry))).map((rootPath) => {
    if (!existsSync(rootPath)) {
      throw new Error(`Allowed root inesistente: ${rootPath}`);
    }
    const stats = statSync(rootPath);
    if (!stats.isDirectory()) {
      throw new Error(`Allowed root non directory: ${rootPath}`);
    }
    return rootPath;
  });
}

export function loadConfig(overrides: Partial<MemoryNodeConfig> = {}): MemoryNodeConfig {
  return { ...DEFAULT_CONFIG, ...overrides };
}

export function validateProjectPath(projectPath: string): string {
  const resolved = resolve(projectPath);
  if (!existsSync(resolved)) {
    throw new Error(`project_path does not exist: ${resolved}`);
  }

  const stats = statSync(resolved);
  if (!stats.isDirectory()) {
    throw new Error(`project_path is not a directory: ${resolved}`);
  }

  return resolved;
}

export function resolveProject(options: ResolveProjectOptions): ProjectResolution {
  const warnings: string[] = [];
  const projectPath = options.projectPath;
  const inputProjectId = options.inputProjectId;

  if (inputProjectId && inputProjectId.trim().length > 0) {
    return { projectId: inputProjectId.trim(), canonicalPath: projectPath, warnings };
  }

  if (projectPath) {
    const envPath = resolve(projectPath, ".env");
    const targetEnv = parseEnvFile(envPath);
    const envProjectId = targetEnv.MEMORY_PROJECT_ID?.trim();
    if (envProjectId) {
      return { projectId: envProjectId, canonicalPath: projectPath, warnings };
    }

    const gitRemote = readGitRemote(projectPath);
    if (gitRemote) {
      return { projectId: `git:${gitRemote}`, canonicalPath: projectPath, warnings };
    }

    warnings.push("Using hashed project_path fallback for project_id.");
    return { projectId: `path:${hashProjectPath(projectPath)}`, canonicalPath: projectPath, warnings };
  }

  throw new Error("Unable to resolve project_id without project_path or explicit project_id.");
}
