const { Client } = require("pg");
const crypto = require("crypto");
require("dotenv").config();
const client = new Client({
  user: process.env.PG_USER,
  password: process.env.PG_PASSWORD,
  database: process.env.PG_DATABASE,
  host: process.env.PG_HOST,
  port: process.env.PG_PORT,
});

client
  .connect()
  .then(() => {
    createTables()
      .then(() => console.log("Tables created successfully"))
      .catch((e) => console.error("Failed to create tables ", e));
    console.log(
      `Successfully connected to database: ${process.env.PG_DATABASE}`,
    );
  })
  .catch((err) =>
    console.error(
      `Failed to connect to database: ${process.env.PG_DATABASE}, ${err}`,
    ),
  );

// Creating database tables
async function createTables() {
  try {
    await client.query(`
      CREATE TABLE IF NOT EXISTS accounts (
        username VARCHAR(20) NOT NULL UNIQUE,
        password VARCHAR(128) NOT NULL,
        user_id VARCHAR(128) PRIMARY KEY,
        created_at TIMESTAMPTZ DEFAULT NOW()
      );
    `);
  } catch (e) {
    console.error("Failed to create accounts table ", e);
  }

  try {
    await client.query(`
      CREATE TABLE IF NOT EXISTS messages (
        sender VARCHAR(128) NOT NULL,
        recipient VARCHAR(128) NOT NULL,
        message VARCHAR(10000) NOT NULL,
        timestamp TIMESTAMPTZ DEFAULT NOW(),
        message_id SERIAL PRIMARY KEY,
        UNIQUE (sender, recipient, message_id)
      );
      CREATE INDEX IF NOT EXISTS idx_messages_conversation
      ON messages (sender, recipient, message_id ASC);
    `);
  } catch (e) {
    console.error("Failed to create messages table ", e);
  }
  try {
    await client.query(`
      CREATE TABLE IF NOT EXISTS contacts (
        username VARCHAR(20) NOT NULL,
        usernameContact VARCHAR(20) NOT NULL,
        read BOOLEAN NOT NULL,
        lastActive VARCHAR(100) NOT NULL,
        CONSTRAINT unique_username_contact UNIQUE (username, usernameContact)
      );
    `);
  } catch (e) {
    console.error("Failed to create messages table ", e);
  }
}

async function insertUser(username, password) {
  const user_id = crypto.randomUUID();

  const query = `
    INSERT INTO accounts (username, password, user_id)
    VALUES ($1, $2, $3);
  `;
  try {
    await client.query(query, [username, password, user_id]);
  } catch (e) {
    console.error("Failed to insert user ", e);
  }
}

async function getUserId(username) {
  const query = `
    SELECT user_id FROM accounts
    WHERE username = $1;
  `;
  try {
    const result = await client.query(query, [username]);
    console.log("GETUSERID: ", result.rows[0].user_id);
    return result.rows[0].user_id;
  } catch (e) {
    console.error("Failed to get user id", e);
  }
}
async function insertMessage(sender, recipient, message) {
  const query = `
    INSERT INTO messages (sender, recipient, message)
    VALUES ($1, $2, $3) 
    RETURNING message, timestamp, message_id;
  `;
  try {
    const results = await client.query(query, [sender, recipient, message]);
    return results.rows[0];
  } catch (e) {
    console.error("Failed to insert message ", e);
  }
}

async function getMessages(username, recipient, limit = 50, cursor = 0) {
  console.log(
    `getMessages for Username: ${username}, recipient: ${recipient}, limit: ${limit}, cursor: ${cursor}`,
  );
  let query;
  let params;

  if (cursor) {
    query = `
      SELECT * FROM messages
      WHERE ((sender = $1 AND recipient = $2) OR (sender = $2 AND recipient = $1))
      AND message_id < $3
      ORDER BY message_id DESC
      LIMIT $4;
    `;
    params = [username, recipient, cursor, limit];
  } else {
    query = `
      SELECT * FROM messages 
      WHERE (sender = $1 AND recipient = $2) OR (sender = $2 AND recipient = $1)
      ORDER BY message_id DESC
      LIMIT $3;
    `;
    params = [username, recipient, limit];
  }

  try {
    const results = await client.query(query, params);
    let messages = results.rows;
    if (!cursor) {
      messages = messages.reverse();
    }

    return messages;
  } catch (e) {
    console.error("Failed to get messages ", e);
  }
}

async function checkUserExist(username) {
  const query = `
    SELECT 1 FROM accounts 
    WHERE LOWER(username) = LOWER($1)
    LIMIT 1;
  `;

  try {
    const result = await client.query(query, [username]);
    return result.rows.length > 0;
  } catch (e) {
    console.error("Failed to check if user exist ", e);
    return false;
  }
}

async function getPassword(username) {
  const query = `
  SELECT password FROM accounts 
  WHERE username = $1;
  `;
  try {
    const result = await client.query(query, [username]);
    return result.rows[0].password;
  } catch (e) {
    console.error("Failed to get user password ", e);
  }
}

async function changePassword(username, newPassword) {
  const query = `
    UPDATE accounts
    SET password = $1
    WHERE username = $2;
  `;
  try {
    await client.query(query, [newPassword, username]);
  } catch (e) {
    console.error("Failed to change password ", e);
  }
}

async function insertContact(username, usernameContact, read) {
  const timestamp = getTime();
  console.log(
    `insertContact username: ${username}, usernameContact: ${usernameContact}, read: ${read}`,
  );

  const query = `
    INSERT INTO contacts (username, usernameContact, lastActive, read)
    VALUES ($1, $2, $3, $4)
    ON CONFLICT ON CONSTRAINT unique_username_contact
    DO NOTHING;
  `;
  try {
    await client.query(query, [username, usernameContact, timestamp, read]);
  } catch (e) {
    console.error("Failed to insert contact ", e);
  }
}

async function getContacts(username) {
  const query = `
    SELECT usernameContact, read FROM contacts
    WHERE username = $1
    ORDER BY lastActive ASC;
  `;
  try {
    const result = await client.query(query, [username]);
    return result.rows;
  } catch (e) {
    console.error("Failed to get contacts ", e);
  }
}

async function deleteContact(username, usernamecontact) {
  const query = `
    DELETE FROM contacts 
    WHERE (LOWER(username) = LOWER($1) AND LOWER(usernamecontact) = LOWER($2))
    RETURNING *;
  `;
  try {
    const result = await client.query(query, [username, usernamecontact]);
    if (result.rowCount === 0) {
      console.log("No matching contact found with:", {
        username,
        usernamecontact,
      });
    } else {
      console.log("Successfully deleted contact");
    }
  } catch (e) {
    console.error("Failed to remove contact ", e);
  }
}

async function updateContactStatus(username, usernamecontact, read) {
  const query = `
  UPDATE contacts SET read = $1 
  WHERE username = $2 AND usernamecontact = $3
  `;
  try {
    await client.query(query, [read, username, usernamecontact]);
    await updateContactLastActive(username, usernamecontact);
    console.log("Successfully updated contact status");
  } catch (e) {
    console.error("Failed to update contact status ", e);
  }
}

async function updateContactLastActive(username, usernamecontact) {
  const timestamp = getTime();
  const query = `
  UPDATE contacts SET lastActive = $1 
  WHERE username = $2 AND usernamecontact = $3
  `;
  try {
    await client.query(query, [timestamp, username, usernamecontact]);
    console.log("Successfully updated contact last active time");
  } catch (e) {
    console.error("Failed to update contact last active time");
  }
}

function getTime() {
  const now = new Date();
  const year = now.getFullYear();
  const month = ("0" + (now.getMonth() + 1)).slice(-2);
  const day = ("0" + now.getDate()).slice(-2);

  const hour = ("0" + now.getHours()).slice(-2);
  const minute = ("0" + now.getMinutes()).slice(-2);
  const second = ("0" + now.getSeconds()).slice(-2);
  return `${year}-${month}-${day} ${hour}:${minute}:${second}`;
}
module.exports = {
  client,
  insertUser,
  insertMessage,
  checkUserExist,
  changePassword,
  getPassword,
  insertContact,
  deleteContact,
  getMessages,
  getUserId,
  getContacts,
  updateContactStatus,
  updateContactLastActive,
};
