package main

import (
	"database/sql"
	"time"

	"github.com/google/uuid"
)

type DodItem struct {
	ID        string `json:"id"`
	RunID     string `json:"run_id"`
	ItemKey   string `json:"item_key"`
	Kind      string `json:"kind"`
	Expected  string `json:"expected"`
	Required  bool   `json:"required"`
	Status    string `json:"status"`
	CreatedAt int64  `json:"created_at"`
}

type DodEvidence struct {
	ID          string  `json:"id"`
	DodItemID   string  `json:"dod_item_id"`
	Kind        string  `json:"kind"`
	PayloadPath *string `json:"payload_path,omitempty"`
	PayloadURL  *string `json:"payload_url,omitempty"`
	PayloadText *string `json:"payload_text,omitempty"`
	AttachedAt  int64   `json:"attached_at"`
	ValidatedAt *int64  `json:"validated_at,omitempty"`
	ValidatedBy *string `json:"validated_by,omitempty"`
}

func createDodItem(db *sql.DB, runID, key, kind, expected string, required bool) (DodItem, error) {
	id := "dod_" + uuid.New().String()[:12]
	now := time.Now().Unix()
	reqInt := 0
	if required {
		reqInt = 1
	}
	_, err := db.Exec(`INSERT INTO dod_items
		(id, run_id, item_key, kind, expected, required, status, created_at)
		VALUES (?, ?, ?, ?, ?, ?, 'pending', ?)`,
		id, runID, key, kind, expected, reqInt, now)
	if err != nil {
		return DodItem{}, err
	}
	return DodItem{
		ID: id, RunID: runID, ItemKey: key, Kind: kind, Expected: expected,
		Required: required, Status: "pending", CreatedAt: now,
	}, nil
}

func listDodItems(db *sql.DB, runID string) ([]DodItem, error) {
	rows, err := db.Query(`SELECT id, run_id, item_key, kind, expected, required, status, created_at
		FROM dod_items WHERE run_id = ? ORDER BY created_at`, runID)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	out := []DodItem{}
	for rows.Next() {
		var it DodItem
		var reqInt int
		if err := rows.Scan(&it.ID, &it.RunID, &it.ItemKey, &it.Kind, &it.Expected, &reqInt, &it.Status, &it.CreatedAt); err != nil {
			return nil, err
		}
		it.Required = reqInt != 0
		out = append(out, it)
	}
	return out, nil
}

func attachEvidence(db *sql.DB, itemID, kind string, path, url, text *string) (DodEvidence, error) {
	id := "ev_" + uuid.New().String()[:12]
	now := time.Now().Unix()
	_, err := db.Exec(`INSERT INTO dod_evidence
		(id, dod_item_id, kind, payload_path, payload_url, payload_text, attached_at)
		VALUES (?, ?, ?, ?, ?, ?, ?)`,
		id, itemID, kind, nullStr(path), nullStr(url), nullStr(text), now)
	if err != nil {
		return DodEvidence{}, err
	}
	// Auto-bump item status to 'done' on first evidence
	_, _ = db.Exec(`UPDATE dod_items SET status = 'done' WHERE id = ? AND status = 'pending'`, itemID)
	return DodEvidence{
		ID: id, DodItemID: itemID, Kind: kind,
		PayloadPath: path, PayloadURL: url, PayloadText: text,
		AttachedAt: now,
	}, nil
}

func validateEvidence(db *sql.DB, evID, validatedBy string) error {
	now := time.Now().Unix()
	res, err := db.Exec(`UPDATE dod_evidence
		SET validated_at = ?, validated_by = ? WHERE id = ?`, now, validatedBy, evID)
	if err != nil {
		return err
	}
	n, _ := res.RowsAffected()
	if n == 0 {
		return sql.ErrNoRows
	}
	// Bump item status to validated
	_, _ = db.Exec(`UPDATE dod_items SET status = 'validated'
		WHERE id = (SELECT dod_item_id FROM dod_evidence WHERE id = ?)`, evID)
	return nil
}

func listEvidence(db *sql.DB, itemID string) ([]DodEvidence, error) {
	rows, err := db.Query(`SELECT id, dod_item_id, kind, payload_path, payload_url, payload_text,
		attached_at, validated_at, validated_by
		FROM dod_evidence WHERE dod_item_id = ? ORDER BY attached_at`, itemID)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	out := []DodEvidence{}
	for rows.Next() {
		var ev DodEvidence
		var path, url, text, valBy sql.NullString
		var valAt sql.NullInt64
		if err := rows.Scan(&ev.ID, &ev.DodItemID, &ev.Kind, &path, &url, &text,
			&ev.AttachedAt, &valAt, &valBy); err != nil {
			return nil, err
		}
		if path.Valid {
			s := path.String
			ev.PayloadPath = &s
		}
		if url.Valid {
			s := url.String
			ev.PayloadURL = &s
		}
		if text.Valid {
			s := text.String
			ev.PayloadText = &s
		}
		if valAt.Valid {
			v := valAt.Int64
			ev.ValidatedAt = &v
		}
		if valBy.Valid {
			s := valBy.String
			ev.ValidatedBy = &s
		}
		out = append(out, ev)
	}
	return out, nil
}

// dodGateOpen returns true when every required item has at least one validated evidence.
func dodGateOpen(db *sql.DB, runID string) (bool, error) {
	row := db.QueryRow(`
		SELECT COUNT(*) FROM dod_items
		WHERE run_id = ? AND required = 1
		  AND status != 'validated'`, runID)
	var n int
	if err := row.Scan(&n); err != nil {
		return false, err
	}
	return n == 0, nil
}

func nullStr(p *string) interface{} {
	if p == nil {
		return nil
	}
	return *p
}
