// Package audit provides tamper-evident audit logging with hash chaining
package audit

import (
	"bufio"
	"context"
	"crypto/sha256"
	"database/sql"
	"encoding/hex"
	"fmt"
	"os"
	"path/filepath"
	"time"
)

// DBCheckpointManager stores chain state in a PostgreSQL database for external tamper detection.
// A root attacker who modifies the local log file cannot also silently modify a remote Postgres instance
// (assuming separate credentials and network controls).
type DBCheckpointManager struct {
	db *sql.DB
}

// NewDBCheckpointManager creates a new database checkpoint manager
func NewDBCheckpointManager(db *sql.DB) *DBCheckpointManager {
	return &DBCheckpointManager{db: db}
}

// Checkpoint stores current chain state in the database
func (dcm *DBCheckpointManager) Checkpoint(seq uint64, hash, fileName string) error {
	fileHash, err := sha256File(fileName)
	if err != nil {
		return fmt.Errorf("hash file for checkpoint: %w", err)
	}

	_, err = dcm.db.Exec(
		`INSERT INTO audit_chain_checkpoints
			(last_seq, last_hash, file_name, file_hash, checkpoint_time)
		 VALUES ($1, $2, $3, $4, $5)`,
		seq, hash, filepath.Base(fileName), fileHash, time.Now().UTC(),
	)
	if err != nil {
		return fmt.Errorf("insert checkpoint: %w", err)
	}
	return nil
}

// VerifyAgainstDB verifies local file against the latest database checkpoint.
// This should be run from a separate host, not the app process itself.
func (dcm *DBCheckpointManager) VerifyAgainstDB(filePath string) error {
	var dbSeq uint64
	var dbHash string
	err := dcm.db.QueryRow(
		`SELECT last_seq, last_hash
		   FROM audit_chain_checkpoints
		  WHERE file_name = $1
		  ORDER BY checkpoint_time DESC
		  LIMIT 1`,
		filepath.Base(filePath),
	).Scan(&dbSeq, &dbHash)
	if err != nil {
		return fmt.Errorf("db checkpoint lookup: %w", err)
	}

	localSeq, localHash, err := getLastEventFromFile(filePath)
	if err != nil {
		return err
	}

	if uint64(localSeq) != dbSeq || localHash != dbHash {
		return fmt.Errorf(
			"TAMPERING DETECTED: local(seq=%d hash=%s) vs db(seq=%d hash=%s)",
			localSeq, localHash, dbSeq, dbHash,
		)
	}

	return nil
}

// VerifyAllFiles checks all known audit files against their latest checkpoints
func (dcm *DBCheckpointManager) VerifyAllFiles() ([]VerificationResult, error) {
	rows, err := dcm.db.Query(
		`SELECT DISTINCT ON (file_name) file_name, last_seq, last_hash
		   FROM audit_chain_checkpoints
		  ORDER BY file_name, checkpoint_time DESC`,
	)
	if err != nil {
		return nil, fmt.Errorf("query checkpoints: %w", err)
	}
	defer rows.Close()

	var results []VerificationResult
	for rows.Next() {
		var fileName string
		var dbSeq uint64
		var dbHash string
		if err := rows.Scan(&fileName, &dbSeq, &dbHash); err != nil {
			continue
		}

		result := VerificationResult{
			Timestamp: time.Now().UTC(),
			Valid:     true,
		}

		localSeq, localHash, err := getLastEventFromFile(fileName)
		if err != nil {
			result.Valid = false
			result.Error = fmt.Sprintf("read local file: %v", err)
		} else if uint64(localSeq) != dbSeq || localHash != dbHash {
			result.Valid = false
			result.FirstTampered = localSeq
			result.Error = fmt.Sprintf(
				"TAMPERING DETECTED: local(seq=%d hash=%s) vs db(seq=%d hash=%s)",
				localSeq, localHash, dbSeq, dbHash,
			)
			result.ChainRootHash = localHash
		}

		results = append(results, result)
	}

	return results, rows.Err()
}

// InitializeSchema creates the required database tables and permissions
func (dcm *DBCheckpointManager) InitializeSchema() error {
	schema := `
CREATE TABLE IF NOT EXISTS audit_chain_checkpoints (
    id              BIGSERIAL PRIMARY KEY,
    checkpoint_time TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    last_seq        BIGINT      NOT NULL,
    last_hash       TEXT        NOT NULL,
    file_name       TEXT        NOT NULL,
    file_hash       TEXT        NOT NULL,
    metadata        JSONB
);

CREATE INDEX IF NOT EXISTS idx_audit_checkpoints_file_time
    ON audit_chain_checkpoints(file_name, checkpoint_time DESC);
`
	_, err := dcm.db.Exec(schema)
	return err
}

// RestrictWriterPermissions revokes UPDATE and DELETE permissions from the audit_writer role.
// This makes the table effectively append-only for the writer user.
func (dcm *DBCheckpointManager) RestrictWriterPermissions(writerRole string) error {
	_, err := dcm.db.Exec(
		fmt.Sprintf("REVOKE UPDATE, DELETE ON audit_chain_checkpoints FROM %s", writerRole),
	)
	return err
}

// ContinuousVerification runs verification at regular intervals and reports issues.
// This should be run as a background goroutine or separate process.
func (dcm *DBCheckpointManager) ContinuousVerification(
	ctx context.Context,
	interval time.Duration,
	filePaths []string,
	alerter AlertManager,
) {
	ticker := time.NewTicker(interval)
	defer ticker.Stop()

	for {
		select {
		case <-ctx.Done():
			return
		case <-ticker.C:
			for _, filePath := range filePaths {
				if err := dcm.VerifyAgainstDB(filePath); err != nil {
					if alerter != nil {
						_ = alerter.Alert(ctx, TamperAlert{
							DetectedAt:  time.Now().UTC(),
							Severity:    "critical",
							Description: fmt.Sprintf("Database checkpoint verification failed for %s", filePath),
							FilePath:    filePath,
						})
					}
				}
			}
		}
	}
}

// sha256File computes the SHA256 hash of a file (reused from rotation.go)
func sha256FileCheckpoint(path string) (string, error) {
	f, err := os.Open(path)
	if err != nil {
		return "", err
	}
	defer f.Close()

	h := sha256.New()
	scanner := bufio.NewScanner(f)
	for scanner.Scan() {
		// Hash the raw line including newline
		h.Write(scanner.Bytes())
		h.Write([]byte{'\n'})
	}

	if err := scanner.Err(); err != nil {
		return "", err
	}

	return hex.EncodeToString(h.Sum(nil)), nil
}
