package storage

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

// ScheduledJob represents a cron-based recurring scan job.
type ScheduledJob struct {
	ID        int64
	Name      string
	CronExpr  string
	ScanPath  string
	Enabled   bool
	Notify    bool
	LastRunAt *time.Time
	NextRunAt *time.Time
	CreatedAt time.Time
}

// SaveScheduledJob inserts a new scheduled job and returns its ID.
func (db *DB) SaveScheduledJob(job ScheduledJob) (int64, error) {
	enabledInt := 0
	if job.Enabled {
		enabledInt = 1
	}
	notifyInt := 0
	if job.Notify {
		notifyInt = 1
	}

	res, err := db.sql.Exec(
		`INSERT INTO scheduled_jobs (name, cron_expr, scan_path, enabled, notify)
		 VALUES (?, ?, ?, ?, ?)`,
		job.Name, job.CronExpr, job.ScanPath, enabledInt, notifyInt,
	)
	if err != nil {
		return 0, fmt.Errorf("inserting scheduled job: %w", err)
	}
	return res.LastInsertId()
}

// ListScheduledJobs returns all scheduled jobs ordered by creation time.
func (db *DB) ListScheduledJobs() ([]ScheduledJob, error) {
	rows, err := db.sql.Query(
		`SELECT id, name, cron_expr, scan_path, enabled, notify, last_run_at, next_run_at, created_at
		 FROM scheduled_jobs ORDER BY created_at ASC`,
	)
	if err != nil {
		return nil, fmt.Errorf("querying scheduled jobs: %w", err)
	}
	defer rows.Close()

	var jobs []ScheduledJob
	for rows.Next() {
		j, err := scanJobRow(rows)
		if err != nil {
			return nil, err
		}
		jobs = append(jobs, j)
	}
	return jobs, rows.Err()
}

// GetScheduledJob returns a single job by ID.
func (db *DB) GetScheduledJob(id int64) (*ScheduledJob, error) {
	row := db.sql.QueryRow(
		`SELECT id, name, cron_expr, scan_path, enabled, notify, last_run_at, next_run_at, created_at
		 FROM scheduled_jobs WHERE id = ?`, id,
	)
	var j ScheduledJob
	var enabledInt, notifyInt int
	var lastRun, nextRun, createdAt sql.NullString
	if err := row.Scan(&j.ID, &j.Name, &j.CronExpr, &j.ScanPath,
		&enabledInt, &notifyInt, &lastRun, &nextRun, &createdAt); err != nil {
		return nil, err
	}
	j.Enabled = enabledInt != 0
	j.Notify = notifyInt != 0
	j.LastRunAt = parseNullTime(lastRun)
	j.NextRunAt = parseNullTime(nextRun)
	if createdAt.Valid {
		t, _ := time.Parse("2006-01-02 15:04:05", createdAt.String)
		j.CreatedAt = t
	}
	return &j, nil
}

// DeleteScheduledJob removes a job by ID and returns rows affected.
func (db *DB) DeleteScheduledJob(id int64) (int64, error) {
	res, err := db.sql.Exec(`DELETE FROM scheduled_jobs WHERE id = ?`, id)
	if err != nil {
		return 0, fmt.Errorf("deleting scheduled job %d: %w", id, err)
	}
	return res.RowsAffected()
}

// UpdateJobLastRun updates the last_run_at timestamp for a job.
func (db *DB) UpdateJobLastRun(id int64, t time.Time) error {
	_, err := db.sql.Exec(
		`UPDATE scheduled_jobs SET last_run_at = ? WHERE id = ?`,
		t.Format("2006-01-02 15:04:05"), id,
	)
	return err
}

// ListEnabledScheduledJobs returns only enabled jobs.
func (db *DB) ListEnabledScheduledJobs() ([]ScheduledJob, error) {
	rows, err := db.sql.Query(
		`SELECT id, name, cron_expr, scan_path, enabled, notify, last_run_at, next_run_at, created_at
		 FROM scheduled_jobs WHERE enabled = 1 ORDER BY created_at ASC`,
	)
	if err != nil {
		return nil, fmt.Errorf("querying enabled scheduled jobs: %w", err)
	}
	defer rows.Close()

	var jobs []ScheduledJob
	for rows.Next() {
		j, err := scanJobRow(rows)
		if err != nil {
			return nil, err
		}
		jobs = append(jobs, j)
	}
	return jobs, rows.Err()
}

func scanJobRow(rows *sql.Rows) (ScheduledJob, error) {
	var j ScheduledJob
	var enabledInt, notifyInt int
	var lastRun, nextRun, createdAt sql.NullString
	if err := rows.Scan(&j.ID, &j.Name, &j.CronExpr, &j.ScanPath,
		&enabledInt, &notifyInt, &lastRun, &nextRun, &createdAt); err != nil {
		return j, fmt.Errorf("scanning scheduled job row: %w", err)
	}
	j.Enabled = enabledInt != 0
	j.Notify = notifyInt != 0
	j.LastRunAt = parseNullTime(lastRun)
	j.NextRunAt = parseNullTime(nextRun)
	if createdAt.Valid {
		t, _ := time.Parse("2006-01-02 15:04:05", createdAt.String)
		j.CreatedAt = t
	}
	return j, nil
}

func parseNullTime(ns sql.NullString) *time.Time {
	if !ns.Valid || ns.String == "" {
		return nil
	}
	// Try multiple formats SQLite may return.
	for _, layout := range []string{
		"2006-01-02 15:04:05",
		"2006-01-02T15:04:05Z",
		time.RFC3339,
	} {
		if t, err := time.Parse(layout, ns.String); err == nil {
			return &t
		}
	}
	return nil
}
