import * as SQLite from 'expo-sqlite';
import { Plant, CareInfo, Language, AppearanceMode, ColorPalette } from '../types';

// ─── DB-Instanz ────────────────────────────────────────────────────────────────

let _db: SQLite.SQLiteDatabase | null = null;
let _isDatabaseInitialized = false;

export const getDb = (): SQLite.SQLiteDatabase => {
  if (!_db) _db = SQLite.openDatabaseSync('greenlens.db');
  return _db;
};

// ─── Schema ────────────────────────────────────────────────────────────────────

export const initDatabase = (): void => {
  if (_isDatabaseInitialized) return;

  const db = getDb();

  try {
    db.execSync(`
      PRAGMA foreign_keys = ON;
      PRAGMA journal_mode = WAL;

      CREATE TABLE IF NOT EXISTS users (
        id        INTEGER PRIMARY KEY AUTOINCREMENT,
        email     TEXT    NOT NULL UNIQUE COLLATE NOCASE,
        name      TEXT    NOT NULL DEFAULT '',
        password_hash TEXT NOT NULL,
        created_at TEXT   NOT NULL DEFAULT (datetime('now'))
      );

      CREATE TABLE IF NOT EXISTS user_settings (
        user_id         INTEGER PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE,
        language        TEXT NOT NULL DEFAULT 'de',
        language_set    INTEGER NOT NULL DEFAULT 0,
        appearance_mode TEXT NOT NULL DEFAULT 'system',
        color_palette   TEXT NOT NULL DEFAULT 'forest',
        profile_image   TEXT,
        onboarding_done INTEGER NOT NULL DEFAULT 0
      );

      CREATE TABLE IF NOT EXISTS plants (
        id                    TEXT PRIMARY KEY,
        user_id               INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
        name                  TEXT NOT NULL,
        botanical_name        TEXT NOT NULL DEFAULT '',
        image_uri             TEXT NOT NULL DEFAULT '',
        date_added            TEXT NOT NULL,
        care_info             TEXT NOT NULL DEFAULT '{}',
        last_watered          TEXT NOT NULL,
        watering_history      TEXT NOT NULL DEFAULT '[]',
        gallery               TEXT NOT NULL DEFAULT '[]',
        description           TEXT,
        notifications_enabled INTEGER NOT NULL DEFAULT 0,
        health_checks         TEXT NOT NULL DEFAULT '[]'
      );

      CREATE TABLE IF NOT EXISTS lexicon_search_history (
        id        INTEGER PRIMARY KEY AUTOINCREMENT,
        user_id   INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
        query     TEXT    NOT NULL,
        created_at TEXT   NOT NULL DEFAULT (datetime('now'))
      );

      CREATE TABLE IF NOT EXISTS app_meta (
        key   TEXT PRIMARY KEY,
        value TEXT NOT NULL
      );
    `);
    _isDatabaseInitialized = true;
  } catch (error) {
    console.error('Failed to initialize SQLite schema.', error);
    throw error;
  }

  // Migration: add language_set column to existing databases
  try {
    db.runSync('ALTER TABLE user_settings ADD COLUMN language_set INTEGER NOT NULL DEFAULT 0');
  } catch (_) { /* column already exists */ }
};

// ─── App Meta ─────────────────────────────────────────────────────────────────

export const AppMetaDb = {
  get(key: string): string | null {
    const row = getDb().getFirstSync<{ value: string }>(
      'SELECT value FROM app_meta WHERE key = ?',
      [key],
    );
    return row?.value ?? null;
  },

  set(key: string, value: string): void {
    getDb().runSync(
      'INSERT INTO app_meta (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value',
      [key, value],
    );
  },
};

// ─── Auth ──────────────────────────────────────────────────────────────────────
// Credential management has moved to the server (server/lib/auth.js + JWT).
// AuthDb only manages the local device user (id=1) used for plants/settings queries.

export interface DbUser {
  id: number;
  email: string;
  name: string;
}

export const AuthDb = {
  // Ensures a local device user exists. Maps email to a unique local ID.
  ensureLocalUser(email: string, name: string): { id: number } {
    const db = getDb();
    const normalizedEmail = email.trim().toLowerCase();
    
    // Check if this specific email already has a local account
    const existing = db.getFirstSync<{ id: number }>('SELECT id FROM users WHERE email = ?', [normalizedEmail]);
    
    if (existing) {
      // Update name just in case it changed on server
      db.runSync('UPDATE users SET name = ? WHERE id = ?', [name.trim(), existing.id]);
      return { id: existing.id };
    }

    // Create a new local user if it doesn't exist
    const result = db.runSync(
      'INSERT INTO users (email, name, password_hash) VALUES (?, ?, ?)',
      [normalizedEmail, name.trim(), 'server-auth'],
    );
    
    const newUserId = result.lastInsertRowId;
    db.runSync('INSERT OR IGNORE INTO user_settings (user_id) VALUES (?)', [newUserId]);
    
    return { id: newUserId };
  },

  getUserById(id: number): DbUser | null {
    const db = getDb();
    const user = db.getFirstSync<DbUser>(
      'SELECT id, email, name FROM users WHERE id = ?',
      [id],
    );
    return user || null;
  },
};

// ─── Settings ──────────────────────────────────────────────────────────────────

export const SettingsDb = {
  get(userId: number) {
    const db = getDb();
    db.runSync('INSERT OR IGNORE INTO user_settings (user_id) VALUES (?)', [userId]);
    return db.getFirstSync<{
      language: string;
      language_set: number;
      appearance_mode: string;
      color_palette: string;
      profile_image: string | null;
      onboarding_done: number;
    }>('SELECT * FROM user_settings WHERE user_id = ?', [userId])!;
  },

  setLanguage(userId: number, lang: Language) {
    getDb().runSync('UPDATE user_settings SET language = ?, language_set = 1 WHERE user_id = ?', [lang, userId]);
  },

  setAppearanceMode(userId: number, mode: AppearanceMode) {
    getDb().runSync('UPDATE user_settings SET appearance_mode = ? WHERE user_id = ?', [mode, userId]);
  },

  setColorPalette(userId: number, palette: ColorPalette) {
    getDb().runSync('UPDATE user_settings SET color_palette = ? WHERE user_id = ?', [palette, userId]);
  },

  setProfileImage(userId: number, uri: string | null) {
    getDb().runSync('UPDATE user_settings SET profile_image = ? WHERE user_id = ?', [uri, userId]);
  },

  setOnboardingDone(userId: number, done: boolean) {
    getDb().runSync(
      'UPDATE user_settings SET onboarding_done = ? WHERE user_id = ?',
      [done ? 1 : 0, userId],
    );
  },

  setName(userId: number, name: string) {
    getDb().runSync('UPDATE users SET name = ? WHERE id = ?', [name.trim(), userId]);
  },
};

// ─── Plants ────────────────────────────────────────────────────────────────────

const DEFAULT_CARE_INFO: CareInfo = {
  waterIntervalDays: 7,
  light: 'Bright indirect light',
  temp: '18-25 C',
};

const safeJsonParse = <T,>(value: unknown, fallback: T, fieldName: string, plantId: string): T => {
  if (typeof value !== 'string' || !value.trim()) return fallback;

  try {
    return JSON.parse(value) as T;
  } catch (error) {
    console.warn('Failed to parse plant JSON field. Falling back to defaults.', {
      plantId,
      fieldName,
      error: error instanceof Error ? error.message : String(error),
    });
    return fallback;
  }
};

const parsePlant = (row: any): Plant => ({
  id: row.id,
  name: row.name,
  botanicalName: row.botanical_name,
  imageUri: row.image_uri,
  dateAdded: row.date_added,
  careInfo: safeJsonParse<CareInfo>(row.care_info, DEFAULT_CARE_INFO, 'care_info', row.id),
  lastWatered: row.last_watered,
  wateringHistory: safeJsonParse<string[]>(row.watering_history, [], 'watering_history', row.id),
  gallery: safeJsonParse<string[]>(row.gallery, [], 'gallery', row.id),
  description: row.description ?? undefined,
  notificationsEnabled: row.notifications_enabled === 1,
  healthChecks: safeJsonParse(row.health_checks, [], 'health_checks', row.id),
});

export const PlantsDb = {
  getAll(userId: number): Plant[] {
    const rows = getDb().getAllSync<any>(
      'SELECT * FROM plants WHERE user_id = ? ORDER BY date_added DESC',
      [userId],
    );
    return rows.map(parsePlant);
  },

  insert(userId: number, plant: Plant): void {
    getDb().runSync(
      `INSERT INTO plants
        (id, user_id, name, botanical_name, image_uri, date_added,
         care_info, last_watered, watering_history, gallery,
         description, notifications_enabled, health_checks)
       VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
      [
        plant.id,
        userId,
        plant.name,
        plant.botanicalName,
        plant.imageUri,
        plant.dateAdded,
        JSON.stringify(plant.careInfo),
        plant.lastWatered,
        JSON.stringify(plant.wateringHistory ?? []),
        JSON.stringify(plant.gallery ?? []),
        plant.description ?? null,
        plant.notificationsEnabled ? 1 : 0,
        JSON.stringify(plant.healthChecks ?? []),
      ],
    );
  },

  update(userId: number, plant: Plant): void {
    getDb().runSync(
      `UPDATE plants SET
        name = ?, botanical_name = ?, image_uri = ?,
        care_info = ?, last_watered = ?, watering_history = ?,
        gallery = ?, description = ?, notifications_enabled = ?, health_checks = ?
       WHERE id = ? AND user_id = ?`,
      [
        plant.name,
        plant.botanicalName,
        plant.imageUri,
        JSON.stringify(plant.careInfo),
        plant.lastWatered,
        JSON.stringify(plant.wateringHistory ?? []),
        JSON.stringify(plant.gallery ?? []),
        plant.description ?? null,
        plant.notificationsEnabled ? 1 : 0,
        JSON.stringify(plant.healthChecks ?? []),
        plant.id,
        userId,
      ],
    );
  },

  delete(userId: number, plantId: string): void {
    getDb().runSync('DELETE FROM plants WHERE id = ? AND user_id = ?', [plantId, userId]);
  },
};

// ─── Lexicon Search History ────────────────────────────────────────────────────

const HISTORY_LIMIT = 10;

const normalize = (v: string) =>
  v.toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g, '').trim().replace(/\s+/g, ' ');

export const LexiconHistoryDb = {
  getAll(userId: number): string[] {
    return getDb()
      .getAllSync<{ query: string }>(
        'SELECT query FROM lexicon_search_history WHERE user_id = ? ORDER BY created_at DESC LIMIT ?',
        [userId, HISTORY_LIMIT],
      )
      .map((r) => r.query);
  },

  add(userId: number, query: string): void {
    const trimmed = query.trim();
    if (!trimmed) return;
    const db = getDb();
    // Duplikate entfernen
    const normalized = normalize(trimmed);
    const existing = db.getAllSync<{ id: number; query: string }>(
      'SELECT id, query FROM lexicon_search_history WHERE user_id = ?',
      [userId],
    );
    for (const row of existing) {
      if (normalize(row.query) === normalized) {
        db.runSync('DELETE FROM lexicon_search_history WHERE id = ?', [row.id]);
      }
    }
    db.runSync(
      'INSERT INTO lexicon_search_history (user_id, query) VALUES (?, ?)',
      [userId, trimmed],
    );
    // Limit halten
    const oldest = db.getAllSync<{ id: number }>(
      'SELECT id FROM lexicon_search_history WHERE user_id = ? ORDER BY created_at DESC LIMIT -1 OFFSET ?',
      [userId, HISTORY_LIMIT],
    );
    for (const row of oldest) {
      db.runSync('DELETE FROM lexicon_search_history WHERE id = ?', [row.id]);
    }
  },

  clear(userId: number): void {
    getDb().runSync('DELETE FROM lexicon_search_history WHERE user_id = ?', [userId]);
  },
};
