package whistleblower

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

	"github.com/google/uuid"
	"github.com/jackc/pgx/v5"
	"github.com/jackc/pgx/v5/pgxpool"
)

// Store handles whistleblower data persistence
type Store struct {
	pool *pgxpool.Pool
}

// NewStore creates a new whistleblower store
func NewStore(pool *pgxpool.Pool) *Store {
	return &Store{pool: pool}
}

// ============================================================================
// Report CRUD Operations
// ============================================================================

// CreateReport creates a new whistleblower report with auto-generated reference number and access key
func (s *Store) CreateReport(ctx context.Context, report *Report) error {
	report.ID = uuid.New()
	now := time.Now().UTC()
	report.CreatedAt = now
	report.UpdatedAt = now
	report.ReceivedAt = now
	report.DeadlineAcknowledgment = now.AddDate(0, 0, 7)  // 7 days per HinSchG
	report.DeadlineFeedback = now.AddDate(0, 3, 0)        // 3 months per HinSchG

	if report.Status == "" {
		report.Status = ReportStatusNew
	}

	report.AccessKey = generateAccessKey()

	year := now.Year()
	seq, err := s.GetNextSequenceNumber(ctx, report.TenantID, year)
	if err != nil {
		return fmt.Errorf("failed to get sequence number: %w", err)
	}
	report.ReferenceNumber = generateReferenceNumber(year, seq)

	if report.AuditTrail == nil {
		report.AuditTrail = []AuditEntry{}
	}
	report.AuditTrail = append(report.AuditTrail, AuditEntry{
		Timestamp: now,
		Action:    "report_created",
		UserID:    "system",
		Details:   "Report submitted",
	})

	auditTrailJSON, _ := json.Marshal(report.AuditTrail)

	_, err = s.pool.Exec(ctx, `
		INSERT INTO whistleblower_reports (
			id, tenant_id, reference_number, access_key,
			category, status, title, description,
			is_anonymous, reporter_name, reporter_email, reporter_phone,
			received_at, deadline_acknowledgment, deadline_feedback,
			acknowledged_at, closed_at, assigned_to,
			audit_trail, resolution,
			created_at, updated_at
		) VALUES (
			$1, $2, $3, $4,
			$5, $6, $7, $8,
			$9, $10, $11, $12,
			$13, $14, $15,
			$16, $17, $18,
			$19, $20,
			$21, $22
		)
	`,
		report.ID, report.TenantID, report.ReferenceNumber, report.AccessKey,
		string(report.Category), string(report.Status), report.Title, report.Description,
		report.IsAnonymous, report.ReporterName, report.ReporterEmail, report.ReporterPhone,
		report.ReceivedAt, report.DeadlineAcknowledgment, report.DeadlineFeedback,
		report.AcknowledgedAt, report.ClosedAt, report.AssignedTo,
		auditTrailJSON, report.Resolution,
		report.CreatedAt, report.UpdatedAt,
	)

	return err
}

// GetReport retrieves a report by ID
func (s *Store) GetReport(ctx context.Context, id uuid.UUID) (*Report, error) {
	var report Report
	var category, status string
	var auditTrailJSON []byte

	err := s.pool.QueryRow(ctx, `
		SELECT
			id, tenant_id, reference_number, access_key,
			category, status, title, description,
			is_anonymous, reporter_name, reporter_email, reporter_phone,
			received_at, deadline_acknowledgment, deadline_feedback,
			acknowledged_at, closed_at, assigned_to,
			audit_trail, resolution,
			created_at, updated_at
		FROM whistleblower_reports WHERE id = $1
	`, id).Scan(
		&report.ID, &report.TenantID, &report.ReferenceNumber, &report.AccessKey,
		&category, &status, &report.Title, &report.Description,
		&report.IsAnonymous, &report.ReporterName, &report.ReporterEmail, &report.ReporterPhone,
		&report.ReceivedAt, &report.DeadlineAcknowledgment, &report.DeadlineFeedback,
		&report.AcknowledgedAt, &report.ClosedAt, &report.AssignedTo,
		&auditTrailJSON, &report.Resolution,
		&report.CreatedAt, &report.UpdatedAt,
	)

	if err == pgx.ErrNoRows {
		return nil, nil
	}
	if err != nil {
		return nil, err
	}

	report.Category = ReportCategory(category)
	report.Status = ReportStatus(status)
	json.Unmarshal(auditTrailJSON, &report.AuditTrail)

	return &report, nil
}

// GetReportByAccessKey retrieves a report by its access key (for public anonymous access)
func (s *Store) GetReportByAccessKey(ctx context.Context, accessKey string) (*Report, error) {
	var id uuid.UUID
	err := s.pool.QueryRow(ctx,
		"SELECT id FROM whistleblower_reports WHERE access_key = $1",
		accessKey,
	).Scan(&id)

	if err == pgx.ErrNoRows {
		return nil, nil
	}
	if err != nil {
		return nil, err
	}

	return s.GetReport(ctx, id)
}

// ListReports lists reports for a tenant with optional filters
func (s *Store) ListReports(ctx context.Context, tenantID uuid.UUID, filters *ReportFilters) ([]Report, int, error) {
	countQuery := "SELECT COUNT(*) FROM whistleblower_reports WHERE tenant_id = $1"
	countArgs := []interface{}{tenantID}
	countArgIdx := 2

	if filters != nil {
		if filters.Status != "" {
			countQuery += fmt.Sprintf(" AND status = $%d", countArgIdx)
			countArgs = append(countArgs, string(filters.Status))
			countArgIdx++
		}
		if filters.Category != "" {
			countQuery += fmt.Sprintf(" AND category = $%d", countArgIdx)
			countArgs = append(countArgs, string(filters.Category))
			countArgIdx++
		}
	}

	var total int
	err := s.pool.QueryRow(ctx, countQuery, countArgs...).Scan(&total)
	if err != nil {
		return nil, 0, err
	}

	query := `
		SELECT
			id, tenant_id, reference_number, access_key,
			category, status, title, description,
			is_anonymous, reporter_name, reporter_email, reporter_phone,
			received_at, deadline_acknowledgment, deadline_feedback,
			acknowledged_at, closed_at, assigned_to,
			audit_trail, resolution,
			created_at, updated_at
		FROM whistleblower_reports WHERE tenant_id = $1`

	args := []interface{}{tenantID}
	argIdx := 2

	if filters != nil {
		if filters.Status != "" {
			query += fmt.Sprintf(" AND status = $%d", argIdx)
			args = append(args, string(filters.Status))
			argIdx++
		}
		if filters.Category != "" {
			query += fmt.Sprintf(" AND category = $%d", argIdx)
			args = append(args, string(filters.Category))
			argIdx++
		}
	}

	query += " ORDER BY created_at DESC"

	if filters != nil && filters.Limit > 0 {
		query += fmt.Sprintf(" LIMIT $%d", argIdx)
		args = append(args, filters.Limit)
		argIdx++

		if filters.Offset > 0 {
			query += fmt.Sprintf(" OFFSET $%d", argIdx)
			args = append(args, filters.Offset)
			argIdx++
		}
	}

	rows, err := s.pool.Query(ctx, query, args...)
	if err != nil {
		return nil, 0, err
	}
	defer rows.Close()

	var reports []Report
	for rows.Next() {
		var report Report
		var category, status string
		var auditTrailJSON []byte

		err := rows.Scan(
			&report.ID, &report.TenantID, &report.ReferenceNumber, &report.AccessKey,
			&category, &status, &report.Title, &report.Description,
			&report.IsAnonymous, &report.ReporterName, &report.ReporterEmail, &report.ReporterPhone,
			&report.ReceivedAt, &report.DeadlineAcknowledgment, &report.DeadlineFeedback,
			&report.AcknowledgedAt, &report.ClosedAt, &report.AssignedTo,
			&auditTrailJSON, &report.Resolution,
			&report.CreatedAt, &report.UpdatedAt,
		)
		if err != nil {
			return nil, 0, err
		}

		report.Category = ReportCategory(category)
		report.Status = ReportStatus(status)
		json.Unmarshal(auditTrailJSON, &report.AuditTrail)

		report.AccessKey = ""
		reports = append(reports, report)
	}

	return reports, total, nil
}

// UpdateReport updates a report
func (s *Store) UpdateReport(ctx context.Context, report *Report) error {
	report.UpdatedAt = time.Now().UTC()

	auditTrailJSON, _ := json.Marshal(report.AuditTrail)

	_, err := s.pool.Exec(ctx, `
		UPDATE whistleblower_reports SET
			category = $2, status = $3, title = $4, description = $5,
			assigned_to = $6, audit_trail = $7, resolution = $8,
			updated_at = $9
		WHERE id = $1
	`,
		report.ID,
		string(report.Category), string(report.Status), report.Title, report.Description,
		report.AssignedTo, auditTrailJSON, report.Resolution,
		report.UpdatedAt,
	)

	return err
}

// AcknowledgeReport acknowledges a report, setting acknowledged_at and adding an audit entry
func (s *Store) AcknowledgeReport(ctx context.Context, id uuid.UUID, userID uuid.UUID) error {
	report, err := s.GetReport(ctx, id)
	if err != nil || report == nil {
		return fmt.Errorf("report not found")
	}

	now := time.Now().UTC()
	report.AcknowledgedAt = &now
	report.Status = ReportStatusAcknowledged
	report.UpdatedAt = now

	report.AuditTrail = append(report.AuditTrail, AuditEntry{
		Timestamp: now,
		Action:    "report_acknowledged",
		UserID:    userID.String(),
		Details:   "Report acknowledged within HinSchG deadline",
	})

	auditTrailJSON, _ := json.Marshal(report.AuditTrail)

	_, err = s.pool.Exec(ctx, `
		UPDATE whistleblower_reports SET
			status = $2, acknowledged_at = $3,
			audit_trail = $4, updated_at = $5
		WHERE id = $1
	`,
		id, string(ReportStatusAcknowledged), now,
		auditTrailJSON, now,
	)

	return err
}

// CloseReport closes a report with a resolution
func (s *Store) CloseReport(ctx context.Context, id uuid.UUID, userID uuid.UUID, resolution string) error {
	report, err := s.GetReport(ctx, id)
	if err != nil || report == nil {
		return fmt.Errorf("report not found")
	}

	now := time.Now().UTC()
	report.ClosedAt = &now
	report.Status = ReportStatusClosed
	report.Resolution = resolution
	report.UpdatedAt = now

	report.AuditTrail = append(report.AuditTrail, AuditEntry{
		Timestamp: now,
		Action:    "report_closed",
		UserID:    userID.String(),
		Details:   "Report closed with resolution: " + resolution,
	})

	auditTrailJSON, _ := json.Marshal(report.AuditTrail)

	_, err = s.pool.Exec(ctx, `
		UPDATE whistleblower_reports SET
			status = $2, closed_at = $3, resolution = $4,
			audit_trail = $5, updated_at = $6
		WHERE id = $1
	`,
		id, string(ReportStatusClosed), now, resolution,
		auditTrailJSON, now,
	)

	return err
}

// DeleteReport deletes a report and its related data (cascading via FK)
func (s *Store) DeleteReport(ctx context.Context, id uuid.UUID) error {
	_, err := s.pool.Exec(ctx, "DELETE FROM whistleblower_measures WHERE report_id = $1", id)
	if err != nil {
		return err
	}
	_, err = s.pool.Exec(ctx, "DELETE FROM whistleblower_messages WHERE report_id = $1", id)
	if err != nil {
		return err
	}
	_, err = s.pool.Exec(ctx, "DELETE FROM whistleblower_reports WHERE id = $1", id)
	return err
}
