package storage

import (
	"context"
	"encoding/json"
	"fmt"
	"time"
)

// DSGVO request status values (PROJ-50).
const (
	DSGVOStatusOpen      = "open"      // angelegt, noch nicht verarbeitet
	DSGVOStatusPartial   = "partial"   // teilweise abgelehnt (Mischung)
	DSGVOStatusCompleted = "completed" // abgeschlossen (0 Treffer, alle löschbar/abgelehnt, oder gelöscht)
	DSGVOStatusFailed    = "failed"    // Auswertung nach Anlage fehlgeschlagen (PROJ-50 Bug-2)
)

// DSGVOAffectedMail is a single mail affected by a DSGVO erasure request.
type DSGVOAffectedMail struct {
	MailID      string     `json:"mail_id"`
	Subject     string     `json:"subject"`
	Date        *time.Time `json:"date,omitempty"`
	RetainUntil *time.Time `json:"retain_until,omitempty"`
	Deletable   bool       `json:"deletable"`             // true wenn retain_until abgelaufen/nicht gesetzt
	Deleted     bool       `json:"deleted"`               // true wenn bereits über Löschmechanismus entfernt
	Reason      string     `json:"reason,omitempty"`      // z.B. "Aufbewahrungspflicht bis 2030-12-31"
}

// DSGVOResultSummary is the aggregated outcome stored as JSON on the request.
type DSGVOResultSummary struct {
	TotalHits int                 `json:"total_hits"`
	Rejected  int                 `json:"rejected"`  // gesetzlich gesperrt
	Deletable int                 `json:"deletable"` // löschbar
	Deleted   int                 `json:"deleted"`   // bereits gelöscht
	Truncated bool                `json:"truncated"` // Treffer über Limit, Ergebnis unvollständig
	Mails     []DSGVOAffectedMail `json:"mails"`
}

// DSGVORequest represents a documented DSGVO erasure request (Art. 17).
type DSGVORequest struct {
	ID               int64               `json:"id"`
	TenantID         *int64              `json:"tenant_id"`
	RequestedAddress string              `json:"requested_address"`
	RequestedBy      string              `json:"requested_by"`
	CreatedAt        time.Time           `json:"created_at"`
	Status           string              `json:"status"`
	ResultSummary    *DSGVOResultSummary `json:"result_summary,omitempty"`
}

// initDSGVOSchema creates the dsgvo_requests table. Safe to call repeatedly.
func (s *Store) initDSGVOSchema(ctx context.Context) {
	_, _ = s.db.Exec(ctx, `CREATE TABLE IF NOT EXISTS dsgvo_requests (
		id                BIGSERIAL PRIMARY KEY,
		tenant_id         BIGINT,
		requested_address TEXT NOT NULL,
		requested_by      TEXT NOT NULL,
		created_at        TIMESTAMPTZ NOT NULL DEFAULT NOW(),
		status            TEXT NOT NULL DEFAULT 'open',
		result_summary    JSONB
	)`)
	_, _ = s.db.Exec(ctx, `CREATE INDEX IF NOT EXISTS idx_dsgvo_requests_tenant ON dsgvo_requests (tenant_id)`)
}

// CreateDSGVORequest inserts a new request and returns its generated ID.
func (s *Store) CreateDSGVORequest(ctx context.Context, tenantID *int64, address, requestedBy string) (*DSGVORequest, error) {
	if s.db == nil {
		return nil, fmt.Errorf("storage: no db")
	}
	req := &DSGVORequest{
		TenantID:         tenantID,
		RequestedAddress: address,
		RequestedBy:      requestedBy,
		Status:           DSGVOStatusOpen,
	}
	err := s.db.QueryRow(ctx,
		`INSERT INTO dsgvo_requests (tenant_id, requested_address, requested_by, status)
		 VALUES ($1,$2,$3,$4) RETURNING id, created_at`,
		tenantID, address, requestedBy, DSGVOStatusOpen,
	).Scan(&req.ID, &req.CreatedAt)
	if err != nil {
		return nil, fmt.Errorf("storage: create dsgvo request: %w", err)
	}
	return req, nil
}

// UpdateDSGVOResult persists the computed result summary and status.
func (s *Store) UpdateDSGVOResult(ctx context.Context, id int64, status string, summary *DSGVOResultSummary) error {
	if s.db == nil {
		return fmt.Errorf("storage: no db")
	}
	js, err := json.Marshal(summary)
	if err != nil {
		return fmt.Errorf("storage: marshal dsgvo summary: %w", err)
	}
	_, err = s.db.Exec(ctx,
		`UPDATE dsgvo_requests SET status=$1, result_summary=$2 WHERE id=$3`,
		status, js, id,
	)
	if err != nil {
		return fmt.Errorf("storage: update dsgvo result: %w", err)
	}
	return nil
}

// MarkDSGVORequestFailed flags a request as failed without touching the result
// summary. Used to clean up a freshly created request whose evaluation aborted
// (PROJ-50 Bug-2), so no orphaned "open" entry remains.
func (s *Store) MarkDSGVORequestFailed(ctx context.Context, id int64) error {
	if s.db == nil {
		return fmt.Errorf("storage: no db")
	}
	_, err := s.db.Exec(ctx,
		`UPDATE dsgvo_requests SET status=$1 WHERE id=$2`, DSGVOStatusFailed, id)
	if err != nil {
		return fmt.Errorf("storage: mark dsgvo failed: %w", err)
	}
	return nil
}

// ListDSGVORequests returns all requests in the given tenant scope, newest first.
// A nil tenantID returns requests with NULL tenant_id (global/superadmin scope).
func (s *Store) ListDSGVORequests(ctx context.Context, tenantID *int64) ([]DSGVORequest, error) {
	if s.db == nil {
		return nil, fmt.Errorf("storage: no db")
	}
	var (
		query string
		args  []interface{}
	)
	if tenantID == nil {
		query = `SELECT id, tenant_id, requested_address, requested_by, created_at, status, result_summary
			 FROM dsgvo_requests WHERE tenant_id IS NULL ORDER BY created_at DESC`
	} else {
		query = `SELECT id, tenant_id, requested_address, requested_by, created_at, status, result_summary
			 FROM dsgvo_requests WHERE tenant_id = $1 ORDER BY created_at DESC`
		args = append(args, *tenantID)
	}
	rows, err := s.db.Query(ctx, query, args...)
	if err != nil {
		return nil, fmt.Errorf("storage: list dsgvo requests: %w", err)
	}
	defer rows.Close()
	var out []DSGVORequest
	for rows.Next() {
		req, err := scanDSGVORow(rows)
		if err != nil {
			return nil, err
		}
		out = append(out, *req)
	}
	return out, rows.Err()
}

// GetDSGVORequest returns a single request by ID within the given tenant scope.
// Returns an error if the request does not exist or belongs to another tenant.
func (s *Store) GetDSGVORequest(ctx context.Context, id int64, tenantID *int64) (*DSGVORequest, error) {
	if s.db == nil {
		return nil, fmt.Errorf("storage: no db")
	}
	var (
		query string
		args  []interface{}
	)
	if tenantID == nil {
		query = `SELECT id, tenant_id, requested_address, requested_by, created_at, status, result_summary
			 FROM dsgvo_requests WHERE id=$1 AND tenant_id IS NULL`
		args = append(args, id)
	} else {
		query = `SELECT id, tenant_id, requested_address, requested_by, created_at, status, result_summary
			 FROM dsgvo_requests WHERE id=$1 AND tenant_id=$2`
		args = append(args, id, *tenantID)
	}
	row := s.db.QueryRow(ctx, query, args...)
	return scanDSGVORow(row)
}

// DSGVOMailMeta holds the metadata needed to evaluate a DSGVO request per mail.
type DSGVOMailMeta struct {
	Subject     string
	ReceivedAt  time.Time
	RetainUntil *time.Time
}

// GetDSGVOMailMeta batch-loads subject, received_at and retain_until for the
// given mail IDs. Missing IDs are omitted. Used by the DSGVO workflow to avoid
// loading and parsing every raw mail file.
//
// tenantID scopes the lookup to a single tenant to prevent cross-tenant leakage
// (PROJ-50 Bug-1): a DSGVO request belongs to exactly one tenant, so only mails
// of that tenant may be evaluated. A nil tenantID is the global/superadmin scope
// and matches mails of every tenant.
func (s *Store) GetDSGVOMailMeta(ctx context.Context, ids []string, tenantID *int64) (map[string]DSGVOMailMeta, error) {
	if s.db == nil || len(ids) == 0 {
		return map[string]DSGVOMailMeta{}, nil
	}
	var (
		query string
		args  []interface{}
	)
	if tenantID == nil {
		query = `SELECT id, subject, received_at, retain_until FROM emails WHERE id = ANY($1)`
		args = []interface{}{ids}
	} else {
		query = `SELECT id, subject, received_at, retain_until FROM emails WHERE id = ANY($1) AND tenant_id = $2`
		args = []interface{}{ids, *tenantID}
	}
	rows, err := s.db.Query(ctx, query, args...)
	if err != nil {
		return nil, fmt.Errorf("storage: dsgvo mail meta: %w", err)
	}
	defer rows.Close()
	out := make(map[string]DSGVOMailMeta, len(ids))
	for rows.Next() {
		var (
			id      string
			subject *string
			recv    time.Time
			until   *time.Time
		)
		if err := rows.Scan(&id, &subject, &recv, &until); err != nil {
			return nil, fmt.Errorf("storage: scan dsgvo mail meta: %w", err)
		}
		m := DSGVOMailMeta{ReceivedAt: recv, RetainUntil: until}
		if subject != nil {
			m.Subject = *subject
		}
		out[id] = m
	}
	return out, rows.Err()
}

type dsgvoScanner interface {
	Scan(dest ...interface{}) error
}

func scanDSGVORow(row dsgvoScanner) (*DSGVORequest, error) {
	var (
		req DSGVORequest
		js  []byte
	)
	if err := row.Scan(&req.ID, &req.TenantID, &req.RequestedAddress, &req.RequestedBy,
		&req.CreatedAt, &req.Status, &js); err != nil {
		return nil, fmt.Errorf("storage: scan dsgvo request: %w", err)
	}
	if len(js) > 0 {
		var sum DSGVOResultSummary
		if err := json.Unmarshal(js, &sum); err == nil {
			req.ResultSummary = &sum
		}
	}
	return &req, nil
}
