package services

import (
	"context"
	"database/sql"
	"encoding/json"
	"errors"
	"fmt"
	"strings"
	"time"

	"github.com/google/uuid"
	"github.com/jmoiron/sqlx"

	"mgit.msbls.de/m/paliad/internal/models"
)

// PartyService reads and writes paliad.parties. Visibility inherits from
// the parent Project.
type PartyService struct {
	db       *sqlx.DB
	projects *ProjectService
}

// NewPartyService wires the service.
func NewPartyService(db *sqlx.DB, projects *ProjectService) *PartyService {
	return &PartyService{db: db, projects: projects}
}

const partyColumns = `id, project_id, name, role, representative, contact_info,
	created_at, updated_at`

// CreatePartyInput is the payload for Create.
type CreatePartyInput struct {
	Name           string          `json:"name"`
	Role           *string         `json:"role,omitempty"`
	Representative *string         `json:"representative,omitempty"`
	ContactInfo    json.RawMessage `json:"contact_info,omitempty"`
}

// ListForProject returns all Parties for the Project, visibility-checked.
func (s *PartyService) ListForProject(ctx context.Context, userID, projectID uuid.UUID) ([]models.Party, error) {
	if _, err := s.projects.GetByID(ctx, userID, projectID); err != nil {
		return nil, err
	}
	rows := []models.Party{}
	if err := s.db.SelectContext(ctx, &rows,
		`SELECT `+partyColumns+`
		   FROM paliad.parties
		  WHERE project_id = $1
		  ORDER BY name`, projectID); err != nil {
		return nil, fmt.Errorf("list parties: %w", err)
	}
	return rows, nil
}

// Create inserts a Party under a Project; visibility is checked on the parent.
func (s *PartyService) Create(ctx context.Context, userID, projectID uuid.UUID, input CreatePartyInput) (*models.Party, error) {
	if strings.TrimSpace(input.Name) == "" {
		return nil, fmt.Errorf("%w: name is required", ErrInvalidInput)
	}
	if _, err := s.projects.GetByID(ctx, userID, projectID); err != nil {
		return nil, err
	}

	contact := input.ContactInfo
	if len(contact) == 0 {
		contact = json.RawMessage(`{}`)
	}

	id := uuid.New()
	now := time.Now().UTC()
	if _, err := s.db.ExecContext(ctx,
		`INSERT INTO paliad.parties
		   (id, project_id, name, role, representative, contact_info,
		    created_at, updated_at)
		 VALUES ($1, $2, $3, $4, $5, $6, $7, $7)`,
		id, projectID, input.Name, input.Role, input.Representative, contact, now,
	); err != nil {
		return nil, fmt.Errorf("insert party: %w", err)
	}

	var p models.Party
	if err := s.db.GetContext(ctx, &p,
		`SELECT `+partyColumns+` FROM paliad.parties WHERE id = $1`, id); err != nil {
		return nil, fmt.Errorf("fetch created party: %w", err)
	}
	return &p, nil
}

// Delete removes a Party. Partner/admin only.
func (s *PartyService) Delete(ctx context.Context, userID, partyID uuid.UUID) error {
	user, err := s.projects.Users().GetByID(ctx, userID)
	if err != nil {
		return err
	}
	if user == nil {
		return ErrNotVisible
	}
	if user.GlobalRole != "global_admin" {
		return fmt.Errorf("%w: only partners/admins can delete Parties", ErrForbidden)
	}

	var projectID uuid.UUID
	err = s.db.GetContext(ctx, &projectID,
		`SELECT project_id FROM paliad.parties WHERE id = $1`, partyID)
	if errors.Is(err, sql.ErrNoRows) {
		return ErrNotVisible
	}
	if err != nil {
		return fmt.Errorf("lookup party parent: %w", err)
	}
	if _, err := s.projects.GetByID(ctx, userID, projectID); err != nil {
		return err
	}
	if _, err := s.db.ExecContext(ctx,
		`DELETE FROM paliad.parties WHERE id = $1`, partyID); err != nil {
		return fmt.Errorf("delete party: %w", err)
	}
	return nil
}
