import { join } from 'path';
import sqlite3 from 'sqlite3';
import { open, Database } from 'sqlite';
import { IcyNetUser } from '../../common/types/user';
import { DatabasePony, DatabaseUser } from '../types/database';

export class Persistence {
  private db!: Database;

  constructor(private _store = join(process.cwd(), 'ponies.db')) {}

  async initialize(): Promise<void> {
    this.db = await open({
      filename: this._store,
      driver: sqlite3.cached.Database,
    });

    await this.db.migrate();
  }

  async upsertUser(user: IcyNetUser): Promise<DatabaseUser> {
    const existing = await this.db.get<DatabaseUser>(
      `SELECT * FROM User WHERE id = ?`,
      user.id,
    );
    if (existing) {
      if (existing.display_name !== user.display_name) {
        existing.display_name = user.display_name;
        await this.db.run(`UPDATE User SET display_name = ? WHERE id = ?`, [
          user.display_name,
          user.id,
        ]);
      }
      return existing;
    }

    const time = new Date().toISOString();
    await this.db.run(
      `INSERT INTO User (id, username, display_name, created_at) VALUES (?,?,?,?)`,
      [user.id, user.username, user.display_name, time],
    );
    return { ...user, created_at: time };
  }

  async upsertPony(
    user: IcyNetUser,
    data: DatabasePony,
  ): Promise<DatabasePony> {
    const pony = await this.db.get<DatabasePony>(
      'SELECT * FROM Pony WHERE user_id = ?',
      [user.id],
    );

    const updateData = this.serializePony({ ...pony, ...data });

    if (pony) {
      await this.db.run(
        `UPDATE Pony SET display_name = ?, position = ?, rotation = ?, character = ? WHERE user_id = ?`,
        [
          user.display_name,
          updateData.position,
          updateData.rotation,
          updateData.character,
          user.id,
        ],
      );
      return this.deserializePony(updateData);
    }

    updateData.created_at = new Date().toISOString();
    updateData.user_id = user.id;

    const keys = this.filterPonyKeys(updateData);
    const insert = Array.from({ length: keys.length }, () => '?').join(',');

    const res = await this.db.run(
      `INSERT INTO Pony (${keys.join(',')}) VALUES (${insert})`,
      keys.map((key) => (updateData as any)[key]),
    );
    updateData.id = res.lastID;

    return this.deserializePony(updateData);
  }

  async getUserPony(user: IcyNetUser): Promise<DatabasePony | null> {
    const result = await this.db.get<DatabasePony>(
      'SELECT * FROM Pony WHERE user_id = ?',
      user.id,
    );

    return result ? this.deserializePony(result) : null;
  }

  private serializePony(data: DatabasePony): DatabasePony {
    return {
      ...data,
      position: Array.isArray(data.position)
        ? JSON.stringify(data.position)
        : data.position,
      rotation: Array.isArray(data.rotation)
        ? JSON.stringify(data.rotation)
        : data.rotation,
      character:
        typeof data.character !== 'string'
          ? JSON.stringify(data.character)
          : data.character,
    };
  }

  private deserializePony(data: DatabasePony): DatabasePony {
    return {
      ...data,
      position: Array.isArray(data.position)
        ? data.position
        : JSON.parse(data.position as string),
      rotation: Array.isArray(data.rotation)
        ? data.rotation
        : JSON.parse(data.rotation as string),
      character:
        typeof data.character !== 'string'
          ? data.character
          : JSON.parse(data.character as string),
    };
  }

  private filterPonyKeys(data: DatabasePony): string[] {
    const keys = [
      'display_name',
      'position',
      'rotation',
      'set.character',
      'created_at',
      'user_id',
    ];
    return Object.keys(data).reduce<string[]>(
      (list, current) => (keys.includes(current) ? [...list, current] : list),
      [],
    );
  }
}
