package server

import (
	"database/sql"
	"fmt"
	"log"
	"time"

	_ "modernc.org/sqlite"
)

// Database handles SQLite operations for the server
type Database struct {
	db *sql.DB
}

// NewDatabase creates a new database connection and initializes tables
func NewDatabase(dbPath string) (*Database, error) {
	db, err := sql.Open("sqlite", dbPath)
	if err != nil {
		return nil, fmt.Errorf("failed to open database: %v", err)
	}

	// Enable foreign keys
	if _, err := db.Exec("PRAGMA foreign_keys = ON"); err != nil {
		db.Close()
		return nil, fmt.Errorf("failed to enable foreign keys: %v", err)
	}

	database := &Database{db: db}
	if err := database.initTables(); err != nil {
		db.Close()
		return nil, fmt.Errorf("failed to initialize tables: %v", err)
	}

	return database, nil
}

// initTables creates the database tables if they don't exist
func (d *Database) initTables() error {
	// Admins table
	_, err := d.db.Exec(`
		CREATE TABLE IF NOT EXISTS admins (
			id INTEGER PRIMARY KEY AUTOINCREMENT,
			username TEXT UNIQUE NOT NULL,
			password_hash TEXT NOT NULL,
			role TEXT NOT NULL DEFAULT 'admin',
			created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
			updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
		)
	`)
	if err != nil {
		return fmt.Errorf("failed to create admins table: %v", err)
	}

	// Admin sessions table
	_, err = d.db.Exec(`
		CREATE TABLE IF NOT EXISTS admin_sessions (
			id INTEGER PRIMARY KEY AUTOINCREMENT,
			admin_id INTEGER NOT NULL,
			token TEXT UNIQUE NOT NULL,
			expires_at DATETIME NOT NULL,
			created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
			FOREIGN KEY (admin_id) REFERENCES admins(id) ON DELETE CASCADE
		)
	`)
	if err != nil {
		return fmt.Errorf("failed to create admin_sessions table: %v", err)
	}

	// Clients table
	_, err = d.db.Exec(`
		CREATE TABLE IF NOT EXISTS clients (
			client_id TEXT PRIMARY KEY,
			api_key TEXT NOT NULL,
			max_size_bytes INTEGER NOT NULL DEFAULT 0,
			dataset TEXT NOT NULL,
			enabled INTEGER NOT NULL DEFAULT 1,
			storage_type TEXT NOT NULL DEFAULT 's3',
			keep_hourly INTEGER DEFAULT 24,
			keep_daily INTEGER DEFAULT 7,
			keep_weekly INTEGER DEFAULT 4,
			keep_monthly INTEGER DEFAULT 12,
			created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
			updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
		)
	`)
	if err != nil {
		return fmt.Errorf("failed to create clients table: %v", err)
	}

	// Snapshots table
	_, err = d.db.Exec(`
		CREATE TABLE IF NOT EXISTS snapshots (
			id INTEGER PRIMARY KEY AUTOINCREMENT,
			client_id TEXT NOT NULL,
			snapshot_id TEXT NOT NULL,
			timestamp DATETIME NOT NULL,
			size_bytes INTEGER NOT NULL DEFAULT 0,
			dataset_name TEXT NOT NULL,
			storage_key TEXT NOT NULL,
			storage_type TEXT NOT NULL,
			compressed INTEGER NOT NULL DEFAULT 0,
			incremental INTEGER NOT NULL DEFAULT 0,
			base_snapshot TEXT,
			created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
			FOREIGN KEY (client_id) REFERENCES clients(client_id) ON DELETE CASCADE,
			UNIQUE(client_id, snapshot_id)
		)
	`)
	if err != nil {
		return fmt.Errorf("failed to create snapshots table: %v", err)
	}

	// Create indexes
	_, err = d.db.Exec(`CREATE INDEX IF NOT EXISTS idx_snapshots_client_id ON snapshots(client_id)`)
	if err != nil {
		return fmt.Errorf("failed to create index: %v", err)
	}

	_, err = d.db.Exec(`CREATE INDEX IF NOT EXISTS idx_snapshots_timestamp ON snapshots(timestamp)`)
	if err != nil {
		return fmt.Errorf("failed to create timestamp index: %v", err)
	}

	_, err = d.db.Exec(`CREATE INDEX IF NOT EXISTS idx_admin_sessions_token ON admin_sessions(token)`)
	if err != nil {
		return fmt.Errorf("failed to create admin_sessions index: %v", err)
	}

	return nil
}

// Close closes the database connection
func (d *Database) Close() error {
	return d.db.Close()
}

// Client operations

// GetClient retrieves a client by ID
func (d *Database) GetClient(clientID string) (*ClientConfig, error) {
	client := &ClientConfig{}
	var enabled int
	var keepHourly, keepDaily, keepWeekly, keepMonthly sql.NullInt64

	query := `SELECT client_id, api_key, max_size_bytes, dataset, enabled, storage_type,
		keep_hourly, keep_daily, keep_weekly, keep_monthly
		FROM clients WHERE client_id = ?`

	err := d.db.QueryRow(query, clientID).Scan(
		&client.ClientID, &client.APIKey, &client.MaxSizeBytes, &client.Dataset,
		&enabled, &client.StorageType,
		&keepHourly, &keepDaily, &keepWeekly, &keepMonthly,
	)
	if err == sql.ErrNoRows {
		return nil, nil
	}
	if err != nil {
		return nil, err
	}

	client.Enabled = enabled == 1

	// Set rotation policy if any values are set
	if keepHourly.Valid || keepDaily.Valid || keepWeekly.Valid || keepMonthly.Valid {
		client.RotationPolicy = &RotationPolicy{
			KeepHourly:  int(keepHourly.Int64),
			KeepDaily:   int(keepDaily.Int64),
			KeepWeekly:  int(keepWeekly.Int64),
			KeepMonthly: int(keepMonthly.Int64),
		}
	}

	return client, nil
}

// GetAllClients retrieves all clients
func (d *Database) GetAllClients() ([]*ClientConfig, error) {
	query := `SELECT client_id, api_key, max_size_bytes, dataset, enabled, storage_type,
		keep_hourly, keep_daily, keep_weekly, keep_monthly
		FROM clients`

	rows, err := d.db.Query(query)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var clients []*ClientConfig
	for rows.Next() {
		client := &ClientConfig{}
		var enabled int
		var keepHourly, keepDaily, keepWeekly, keepMonthly sql.NullInt64

		err := rows.Scan(
			&client.ClientID, &client.APIKey, &client.MaxSizeBytes, &client.Dataset,
			&enabled, &client.StorageType,
			&keepHourly, &keepDaily, &keepWeekly, &keepMonthly,
		)
		if err != nil {
			return nil, err
		}

		client.Enabled = enabled == 1

		if keepHourly.Valid || keepDaily.Valid || keepWeekly.Valid || keepMonthly.Valid {
			client.RotationPolicy = &RotationPolicy{
				KeepHourly:  int(keepHourly.Int64),
				KeepDaily:   int(keepDaily.Int64),
				KeepWeekly:  int(keepWeekly.Int64),
				KeepMonthly: int(keepMonthly.Int64),
			}
		}

		clients = append(clients, client)
	}

	return clients, nil
}

// SaveClient saves or updates a client
func (d *Database) SaveClient(client *ClientConfig) error {
	var keepHourly, keepDaily, keepWeekly, keepMonthly interface{}
	if client.RotationPolicy != nil {
		keepHourly = client.RotationPolicy.KeepHourly
		keepDaily = client.RotationPolicy.KeepDaily
		keepWeekly = client.RotationPolicy.KeepWeekly
		keepMonthly = client.RotationPolicy.KeepMonthly
	}

	enabled := 0
	if client.Enabled {
		enabled = 1
	}

	query := `INSERT OR REPLACE INTO clients 
		(client_id, api_key, max_size_bytes, dataset, enabled, storage_type,
		keep_hourly, keep_daily, keep_weekly, keep_monthly, updated_at)
		VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)`

	_, err := d.db.Exec(query,
		client.ClientID, client.APIKey, client.MaxSizeBytes, client.Dataset,
		enabled, client.StorageType,
		keepHourly, keepDaily, keepWeekly, keepMonthly,
	)
	return err
}

// Snapshot operations

// SaveSnapshot saves a new snapshot record
func (d *Database) SaveSnapshot(metadata *SnapshotMetadata) error {
	compressed := 0
	if metadata.Compressed {
		compressed = 1
	}
	incremental := 0
	if metadata.Incremental {
		incremental = 1
	}

	query := `INSERT INTO snapshots 
		(client_id, snapshot_id, timestamp, size_bytes, dataset_name, 
		storage_key, storage_type, compressed, incremental, base_snapshot)
		VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`

	_, err := d.db.Exec(query,
		metadata.ClientID, metadata.SnapshotID, metadata.Timestamp, metadata.SizeBytes,
		metadata.DatasetName, metadata.StorageKey, metadata.StorageType,
		compressed, incremental, metadata.BaseSnapshot,
	)
	return err
}

// GetSnapshotsByClient retrieves all snapshots for a client
func (d *Database) GetSnapshotsByClient(clientID string) ([]*SnapshotMetadata, error) {
	query := `SELECT client_id, snapshot_id, timestamp, size_bytes, dataset_name,
		storage_key, storage_type, compressed, incremental, base_snapshot
		FROM snapshots WHERE client_id = ? ORDER BY timestamp DESC`

	rows, err := d.db.Query(query, clientID)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var snapshots []*SnapshotMetadata
	for rows.Next() {
		snap := &SnapshotMetadata{}
		var compressed, incremental int
		var baseSnapshot sql.NullString

		err := rows.Scan(
			&snap.ClientID, &snap.SnapshotID, &snap.Timestamp, &snap.SizeBytes,
			&snap.DatasetName, &snap.StorageKey, &snap.StorageType,
			&compressed, &incremental, &baseSnapshot,
		)
		if err != nil {
			return nil, err
		}

		snap.Compressed = compressed == 1
		snap.Incremental = incremental == 1
		if baseSnapshot.Valid {
			snap.BaseSnapshot = baseSnapshot.String
		}

		snapshots = append(snapshots, snap)
	}

	return snapshots, nil
}

// GetClientUsage calculates total storage used by a client
func (d *Database) GetClientUsage(clientID string) (int64, error) {
	var total sql.NullInt64
	err := d.db.QueryRow(`SELECT SUM(size_bytes) FROM snapshots WHERE client_id = ?`, clientID).Scan(&total)
	if err != nil {
		return 0, err
	}
	if !total.Valid {
		return 0, nil
	}
	return total.Int64, nil
}

// DeleteSnapshot deletes a snapshot record
func (d *Database) DeleteSnapshot(clientID, snapshotID string) error {
	_, err := d.db.Exec(`DELETE FROM snapshots WHERE client_id = ? AND snapshot_id = ?`, clientID, snapshotID)
	return err
}

// GetOldestSnapshots gets the oldest snapshots for a client (for rotation)
func (d *Database) GetOldestSnapshots(clientID string, limit int) ([]*SnapshotMetadata, error) {
	query := `SELECT client_id, snapshot_id, timestamp, size_bytes, dataset_name,
		storage_key, storage_type, compressed, incremental, base_snapshot
		FROM snapshots WHERE client_id = ? ORDER BY timestamp ASC LIMIT ?`

	rows, err := d.db.Query(query, clientID, limit)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var snapshots []*SnapshotMetadata
	for rows.Next() {
		snap := &SnapshotMetadata{}
		var compressed, incremental int
		var baseSnapshot sql.NullString

		err := rows.Scan(
			&snap.ClientID, &snap.SnapshotID, &snap.Timestamp, &snap.SizeBytes,
			&snap.DatasetName, &snap.StorageKey, &snap.StorageType,
			&compressed, &incremental, &baseSnapshot,
		)
		if err != nil {
			return nil, err
		}

		snap.Compressed = compressed == 1
		snap.Incremental = incremental == 1
		if baseSnapshot.Valid {
			snap.BaseSnapshot = baseSnapshot.String
		}

		snapshots = append(snapshots, snap)
	}

	return snapshots, nil
}

// CreateDefaultClient creates a default client if none exists
func (d *Database) CreateDefaultClient() error {
	clients, err := d.GetAllClients()
	if err != nil {
		return err
	}

	if len(clients) > 0 {
		return nil
	}

	log.Println("No clients found, creating default client 'client1'")
	defaultClient := &ClientConfig{
		ClientID:     "client1",
		APIKey:       hashAPIKey("secret123"),
		MaxSizeBytes: 100 * 1024 * 1024 * 1024, // 100GB
		Dataset:      "backup/client1",
		Enabled:      true,
		StorageType:  "s3",
		RotationPolicy: &RotationPolicy{
			KeepHourly:  24,
			KeepDaily:   7,
			KeepWeekly:  4,
			KeepMonthly: 12,
		},
	}

	return d.SaveClient(defaultClient)
}

// GetSnapshotByID retrieves a specific snapshot
func (d *Database) GetSnapshotByID(clientID, snapshotID string) (*SnapshotMetadata, error) {
	snap := &SnapshotMetadata{}
	var compressed, incremental int
	var baseSnapshot sql.NullString

	query := `SELECT client_id, snapshot_id, timestamp, size_bytes, dataset_name,
		storage_key, storage_type, compressed, incremental, base_snapshot
		FROM snapshots WHERE client_id = ? AND snapshot_id = ?`

	err := d.db.QueryRow(query, clientID, snapshotID).Scan(
		&snap.ClientID, &snap.SnapshotID, &snap.Timestamp, &snap.SizeBytes,
		&snap.DatasetName, &snap.StorageKey, &snap.StorageType,
		&compressed, &incremental, &baseSnapshot,
	)
	if err == sql.ErrNoRows {
		return nil, nil
	}
	if err != nil {
		return nil, err
	}

	snap.Compressed = compressed == 1
	snap.Incremental = incremental == 1
	if baseSnapshot.Valid {
		snap.BaseSnapshot = baseSnapshot.String
	}

	return snap, nil
}

// GetAllSnapshots retrieves all snapshots (for admin purposes)
func (d *Database) GetAllSnapshots() ([]*SnapshotMetadata, error) {
	query := `SELECT client_id, snapshot_id, timestamp, size_bytes, dataset_name,
		storage_key, storage_type, compressed, incremental, base_snapshot
		FROM snapshots ORDER BY timestamp DESC`

	rows, err := d.db.Query(query)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var snapshots []*SnapshotMetadata
	for rows.Next() {
		snap := &SnapshotMetadata{}
		var compressed, incremental int
		var baseSnapshot sql.NullString

		err := rows.Scan(
			&snap.ClientID, &snap.SnapshotID, &snap.Timestamp, &snap.SizeBytes,
			&snap.DatasetName, &snap.StorageKey, &snap.StorageType,
			&compressed, &incremental, &baseSnapshot,
		)
		if err != nil {
			return nil, err
		}

		snap.Compressed = compressed == 1
		snap.Incremental = incremental == 1
		if baseSnapshot.Valid {
			snap.BaseSnapshot = baseSnapshot.String
		}

		snapshots = append(snapshots, snap)
	}

	return snapshots, nil
}

// GetTotalSnapshotCount returns the total number of snapshots
func (d *Database) GetTotalSnapshotCount() (int, error) {
	var count int
	err := d.db.QueryRow(`SELECT COUNT(*) FROM snapshots`).Scan(&count)
	return count, err
}

// GetTotalStorageUsed returns the total storage used across all clients
func (d *Database) GetTotalStorageUsed() (int64, error) {
	var total sql.NullInt64
	err := d.db.QueryRow(`SELECT SUM(size_bytes) FROM snapshots`).Scan(&total)
	if err != nil {
		return 0, err
	}
	if !total.Valid {
		return 0, nil
	}
	return total.Int64, nil
}

// UpdateSnapshotTimestamp updates the timestamp of a snapshot (for rotation tracking)
func (d *Database) UpdateSnapshotTimestamp(clientID, snapshotID string, timestamp time.Time) error {
	_, err := d.db.Exec(`UPDATE snapshots SET timestamp = ? WHERE client_id = ? AND snapshot_id = ?`,
		timestamp, clientID, snapshotID)
	return err
}

// Admin operations

// Admin represents an admin user
type Admin struct {
	ID           int
	Username     string
	PasswordHash string
	Role         string
	CreatedAt    time.Time
	UpdatedAt    time.Time
}

// AdminSession represents an admin session
type AdminSession struct {
	ID        int
	AdminID   int
	Token     string
	ExpiresAt time.Time
	CreatedAt time.Time
}

// CreateAdmin creates a new admin user
func (d *Database) CreateAdmin(username, passwordHash, role string) error {
	_, err := d.db.Exec(`
		INSERT INTO admins (username, password_hash, role) VALUES (?, ?, ?)
	`, username, passwordHash, role)
	return err
}

// GetAdminByUsername retrieves an admin by username
func (d *Database) GetAdminByUsername(username string) (*Admin, error) {
	admin := &Admin{}
	err := d.db.QueryRow(`
		SELECT id, username, password_hash, role, created_at, updated_at
		FROM admins WHERE username = ?
	`, username).Scan(&admin.ID, &admin.Username, &admin.PasswordHash, &admin.Role, &admin.CreatedAt, &admin.UpdatedAt)
	if err == sql.ErrNoRows {
		return nil, nil
	}
	if err != nil {
		return nil, err
	}
	return admin, nil
}

// GetAdminByID retrieves an admin by ID
func (d *Database) GetAdminByID(id int) (*Admin, error) {
	admin := &Admin{}
	err := d.db.QueryRow(`
		SELECT id, username, password_hash, role, created_at, updated_at
		FROM admins WHERE id = ?
	`, id).Scan(&admin.ID, &admin.Username, &admin.PasswordHash, &admin.Role, &admin.CreatedAt, &admin.UpdatedAt)
	if err == sql.ErrNoRows {
		return nil, nil
	}
	if err != nil {
		return nil, err
	}
	return admin, nil
}

// CreateSession creates a new admin session
func (d *Database) CreateSession(adminID int, token string, expiresAt time.Time) error {
	_, err := d.db.Exec(`
		INSERT INTO admin_sessions (admin_id, token, expires_at) VALUES (?, ?, ?)
	`, adminID, token, expiresAt)
	return err
}

// GetSessionByToken retrieves a session by token
func (d *Database) GetSessionByToken(token string) (*AdminSession, error) {
	session := &AdminSession{}
	err := d.db.QueryRow(`
		SELECT id, admin_id, token, expires_at, created_at
		FROM admin_sessions WHERE token = ?
	`, token).Scan(&session.ID, &session.AdminID, &session.Token, &session.ExpiresAt, &session.CreatedAt)
	if err == sql.ErrNoRows {
		return nil, nil
	}
	if err != nil {
		return nil, err
	}
	return session, nil
}

// DeleteSession deletes a session (logout)
func (d *Database) DeleteSession(token string) error {
	_, err := d.db.Exec(`DELETE FROM admin_sessions WHERE token = ?`, token)
	return err
}

// CleanExpiredSessions removes all expired sessions
func (d *Database) CleanExpiredSessions() error {
	_, err := d.db.Exec(`DELETE FROM admin_sessions WHERE expires_at < CURRENT_TIMESTAMP`)
	return err
}

// CreateDefaultAdmin creates a default admin if none exists
func (d *Database) CreateDefaultAdmin() error {
	var count int
	err := d.db.QueryRow(`SELECT COUNT(*) FROM admins`).Scan(&count)
	if err != nil {
		return err
	}

	if count > 0 {
		return nil
	}

	log.Println("No admins found, creating default admin 'admin'")
	// Default password: admin123
	defaultPasswordHash := hashAPIKey("admin123")
	return d.CreateAdmin("admin", defaultPasswordHash, "admin")
}

// DeleteClient deletes a client and all its snapshots
func (d *Database) DeleteClient(clientID string) error {
	_, err := d.db.Exec(`DELETE FROM clients WHERE client_id = ?`, clientID)
	return err
}

// GetAllAdmins retrieves all admins
func (d *Database) GetAllAdmins() ([]*Admin, error) {
	rows, err := d.db.Query(`
		SELECT id, username, password_hash, role, created_at, updated_at
		FROM admins ORDER BY id
	`)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var admins []*Admin
	for rows.Next() {
		admin := &Admin{}
		err := rows.Scan(&admin.ID, &admin.Username, &admin.PasswordHash, &admin.Role, &admin.CreatedAt, &admin.UpdatedAt)
		if err != nil {
			return nil, err
		}
		admins = append(admins, admin)
	}
	return admins, nil
}

// DeleteAdmin deletes an admin by ID
func (d *Database) DeleteAdmin(id int) error {
	_, err := d.db.Exec(`DELETE FROM admins WHERE id = ?`, id)
	return err
}

// UpdateAdminPassword updates an admin's password
func (d *Database) UpdateAdminPassword(id int, passwordHash string) error {
	_, err := d.db.Exec(`UPDATE admins SET password_hash = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?`, passwordHash, id)
	return err
}
