package db

import (
	"api/structs"
	"context"
	"fmt"
	"log"
	"time"

	"github.com/jackc/pgx/v5"
	"github.com/jackc/pgx/v5/pgxpool"
	"golang.org/x/crypto/bcrypt"
)

type DBServer struct {
	Connection *pgxpool.Pool
}

func NewDBServer(connStr string) (*DBServer, error) {
	pool, err := pgxpool.New(context.Background(), connStr)
	if err != nil {
		log.Fatalf("Unable to connect to database: %v\n", err)
	}
	fmt.Println("Database connection established")
	return &DBServer{Connection: pool}, err
}

// ===== Refresh Tokens =====

// StoreRefreshToken stores a hashed refresh token in the database.
func (s *DBServer) StoreRefreshToken(tokenHash string, userID int64, expiresAt time.Time) error {
	_, err := s.Connection.Exec(context.Background(),
		`INSERT INTO refresh_tokens (token_hash, user_id, expires_at) 
		 VALUES ($1, $2, $3);`,
		tokenHash, userID, expiresAt,
	)
	if err != nil {
		return fmt.Errorf("failed to store refresh token: %w", err)
	}

	return nil
}

// ValidateRefreshToken checks if a token exists and is not expired, returning the UserID.
func (s *DBServer) ValidateRefreshToken(tokenHash string) (int64, error) {
	var userID int64
	var expiresAt time.Time

	err := s.Connection.QueryRow(context.Background(),
		`SELECT user_id, expires_at FROM refresh_tokens WHERE token_hash = $1;`,
		tokenHash,
	).Scan(&userID, &expiresAt)

	if err != nil {
		return 0, fmt.Errorf("invalid refresh token: %w", err)
	}

	// Check if the token is expired
	if time.Now().After(expiresAt) {
		// Clean up the expired token
		_ = s.DeleteRefreshToken(tokenHash)
		return 0, fmt.Errorf("refresh token is expired")
	}

	return userID, nil
}

// DeleteRefreshToken removes a token from the database (used for rotation or logout).
func (s *DBServer) DeleteRefreshToken(tokenHash string) error {
	_, err := s.Connection.Exec(context.Background(),
		`DELETE FROM refresh_tokens WHERE token_hash = $1;`,
		tokenHash,
	)
	if err != nil {
		return fmt.Errorf("failed to delete refresh token: %w", err)
	}
	return nil
}

// ===== Users =====

func (s *DBServer) ListUsers() ([]structs.User, error) {
	rows, err := s.Connection.Query(context.Background(), "SELECT id, username FROM users;")
	if err != nil {
		return nil, fmt.Errorf("query failed: %w", err)
	}
	defer rows.Close()

	var users []structs.User
	for rows.Next() {
		var u structs.User
		if err := rows.Scan(&u.ID, &u.Username); err != nil {
			return nil, fmt.Errorf("scan failed: %w", err)
		}
		users = append(users, u)
	}
	return users, nil
}

func (s *DBServer) CreateUser(u structs.User) (structs.User, error) {
	var created structs.User

	hash, err := bcrypt.GenerateFromPassword([]byte(u.Password), bcrypt.DefaultCost)
	if err != nil {
		return created, fmt.Errorf("failed to generate hash: %w", err)
	}

	tx, err := s.Connection.BeginTx(context.Background(), pgx.TxOptions{
		IsoLevel: pgx.Serializable,
	})
	if err != nil {
		return created, fmt.Errorf("failed to begin transaction: %w", err)
	}
	defer tx.Rollback(context.Background())

	err = tx.QueryRow(context.Background(),
		"INSERT INTO users (username, password) VALUES ($1, $2) RETURNING id, username;",
		u.Username, hash,
	).Scan(&created.ID, &created.Username)
	if err != nil {
		return created, fmt.Errorf("insert failed: %w", err)
	}

	if err := tx.Commit(context.Background()); err != nil {
		return created, fmt.Errorf("commit failed: %w", err)
	}

	return created, nil
}

func (s *DBServer) GetUser(id string) (structs.User, error) {
	var u structs.User

	err := s.Connection.QueryRow(context.Background(), "SELECT id, username FROM users WHERE id = $1;", id).Scan(&u.ID, &u.Username)
	if err != nil {
		return u, fmt.Errorf("query failed: %w", err)
	}

	return u, nil
}

func (s *DBServer) GetUserByUsername(username string) (structs.User, error) {
	var u structs.User

	err := s.Connection.QueryRow(context.Background(),
		"SELECT id, username, password FROM users WHERE username = $1;", username,
	).Scan(&u.ID, &u.Username, &u.Password)
	if err != nil {
		return u, fmt.Errorf("query failed: %w", err)
	}

	return u, nil
}

func (s *DBServer) DeleteUser(id string) error {
	tx, err := s.Connection.BeginTx(context.Background(), pgx.TxOptions{
		IsoLevel: pgx.Serializable,
	})
	if err != nil {
		return fmt.Errorf("failed to begin transaction: %w", err)
	}
	defer tx.Rollback(context.Background())

	_, err = tx.Exec(context.Background(), "DELETE FROM users WHERE id = $1;", id)
	if err != nil {
		return fmt.Errorf("delete failed: %w", err)
	}

	if err := tx.Commit(context.Background()); err != nil {
		return fmt.Errorf("commit failed: %w", err)
	}

	return nil
}

// ===== Vaults =====

func (s *DBServer) ListVaults(userId string) ([]structs.Vault, error) {
	rows, err := s.Connection.Query(context.Background(), "SELECT id, name, userid FROM vaults WHERE userid = $1;", userId)
	if err != nil {
		return nil, fmt.Errorf("query failed: %w", err)
	}
	defer rows.Close()

	var vaults []structs.Vault
	for rows.Next() {
		var v structs.Vault
		if err := rows.Scan(&v.ID, &v.Name, &v.UserID); err != nil {
			return nil, fmt.Errorf("scan failed: %w", err)
		}
		vaults = append(vaults, v)
	}
	return vaults, nil
}

func (s *DBServer) CreateVault(v structs.Vault) (structs.Vault, error) {
	var created structs.Vault

	tx, err := s.Connection.BeginTx(context.Background(), pgx.TxOptions{
		IsoLevel: pgx.Serializable,
	})
	if err != nil {
		return created, fmt.Errorf("failed to begin transaction: %w", err)
	}
	defer tx.Rollback(context.Background())

	err = tx.QueryRow(context.Background(),
		"INSERT INTO vaults (name, userid) VALUES ($1, $2) RETURNING id, name, userid;",
		v.Name, v.UserID,
	).Scan(&created.ID, &created.Name, &created.UserID)
	if err != nil {
		return created, fmt.Errorf("insert failed: %w", err)
	}

	if err := tx.Commit(context.Background()); err != nil {
		return created, fmt.Errorf("commit failed: %w", err)
	}

	return created, nil
}

func (s *DBServer) GetVault(id string) (structs.Vault, error) {
	var v structs.Vault

	tx, err := s.Connection.BeginTx(context.Background(), pgx.TxOptions{
		IsoLevel: pgx.Serializable,
	})
	if err != nil {
		return v, fmt.Errorf("failed to begin transaction: %w", err)
	}
	defer tx.Rollback(context.Background())

	err = tx.QueryRow(context.Background(), "SELECT id, name, userid FROM vaults WHERE id = $1;", id).Scan(&v.ID, &v.Name, &v.UserID)
	if err != nil {
		return v, fmt.Errorf("query failed: %w", err)
	}

	if err := tx.Commit(context.Background()); err != nil {
		return v, fmt.Errorf("commit failed: %w", err)
	}

	return v, nil
}

func (s *DBServer) DeleteVault(id string) error {
	tx, err := s.Connection.BeginTx(context.Background(), pgx.TxOptions{
		IsoLevel: pgx.Serializable,
	})
	if err != nil {
		return fmt.Errorf("failed to begin transaction: %w", err)
	}
	defer tx.Rollback(context.Background())

	_, err = tx.Exec(context.Background(), "DELETE FROM vaults WHERE id = $1;", id)
	if err != nil {
		return fmt.Errorf("delete failed: %w", err)
	}

	if err := tx.Commit(context.Background()); err != nil {
		return fmt.Errorf("commit failed: %w", err)
	}

	return nil
}

// ===== Passwords =====

func (s *DBServer) ListPasswords(vaultId string) ([]structs.PasswordEntry, error) {
	rows, err := s.Connection.Query(context.Background(),
		"SELECT id, vaultid, name, url, username, password, iv, tag, notes FROM passwords WHERE vaultid = $1;", vaultId)
	if err != nil {
		return nil, fmt.Errorf("query failed: %w", err)
	}
	defer rows.Close()

	var passwords []structs.PasswordEntry
	for rows.Next() {
		var p structs.PasswordEntry
		if err := rows.Scan(&p.ID, &p.VaultID, &p.Name, &p.URL, &p.Username, &p.Password, &p.IV, &p.Tag, &p.Notes); err != nil {
			return nil, fmt.Errorf("scan failed: %w", err)
		}
		passwords = append(passwords, p)
	}
	return passwords, nil
}

func (s *DBServer) CreatePassword(p structs.PasswordEntry) (structs.PasswordEntry, error) {
	var created structs.PasswordEntry

	tx, err := s.Connection.BeginTx(context.Background(), pgx.TxOptions{
		IsoLevel: pgx.Serializable,
	})
	if err != nil {
		return created, fmt.Errorf("failed to begin transaction: %w", err)
	}
	defer tx.Rollback(context.Background())

	err = tx.QueryRow(context.Background(),
		`INSERT INTO passwords (vaultid, name, url, username, password, iv, tag, notes) 
		 VALUES ($1, $2, $3, $4, $5, $6, $7 ,$8) 
		 RETURNING id, vaultid, name, url, username, password, iv, tag, notes;`,
		p.VaultID, p.Name, p.URL, p.Username, p.Password, p.IV, p.Tag, p.Notes,
	).Scan(&created.ID, &created.VaultID, &created.Name, &created.URL, &created.Username, &created.Password, &created.IV, &created.Tag, &created.Notes)
	if err != nil {
		return created, fmt.Errorf("insert failed: %w", err)
	}

	if err := tx.Commit(context.Background()); err != nil {
		return created, fmt.Errorf("commit failed: %w", err)
	}

	return created, nil
}

func (s *DBServer) GetPassword(id string) (structs.PasswordEntry, error) {
	var p structs.PasswordEntry

	err := s.Connection.QueryRow(context.Background(),
		"SELECT id, vaultid, name, url, username, password, iv, tag, notes FROM passwords WHERE id = $1;", id,
	).Scan(&p.ID, &p.VaultID, &p.Name, &p.URL, &p.Username, &p.Password, &p.IV, &p.Tag, &p.Notes)
	if err != nil {
		return p, fmt.Errorf("query failed: %w", err)
	}

	return p, nil
}

func (s *DBServer) UpdatePassword(p structs.PasswordEntry) (structs.PasswordEntry, error) {
	var updated structs.PasswordEntry

	tx, err := s.Connection.BeginTx(context.Background(), pgx.TxOptions{
		IsoLevel: pgx.Serializable,
	})
	if err != nil {
		return updated, fmt.Errorf("failed to begin transaction: %w", err)
	}
	defer tx.Rollback(context.Background())

	err = tx.QueryRow(context.Background(),
		`UPDATE passwords 
		 SET name = $1, url = $2, username = $3, password = $4, iv = $5, tag = $6, notes = $7 
		 WHERE id = $8 
		 RETURNING id, vaultid, name, url, username, password, iv, tag, notes;`,
		p.Name, p.URL, p.Username, p.Password, p.IV, p.Tag, p.Notes, p.ID,
	).Scan(&updated.ID, &updated.VaultID, &updated.Name, &updated.URL, &updated.Username, &updated.Password, &updated.IV, &updated.Tag, &updated.Notes)
	if err != nil {
		return updated, fmt.Errorf("update failed: %w", err)
	}

	if err := tx.Commit(context.Background()); err != nil {
		return updated, fmt.Errorf("commit failed: %w", err)
	}

	return updated, nil
}

func (s *DBServer) DeletePassword(id string) error {
	tx, err := s.Connection.BeginTx(context.Background(), pgx.TxOptions{
		IsoLevel: pgx.Serializable,
	})
	if err != nil {
		return fmt.Errorf("failed to begin transaction: %w", err)
	}
	defer tx.Rollback(context.Background())

	_, err = tx.Exec(context.Background(), "DELETE FROM passwords WHERE id = $1;", id)
	if err != nil {
		return fmt.Errorf("delete failed: %w", err)
	}

	if err := tx.Commit(context.Background()); err != nil {
		return fmt.Errorf("commit failed: %w", err)
	}

	return nil
}
