package services

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

	"mgit.msbls.de/m/KanzlAI-mGMT/internal/models"

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

type CaseService struct {
	db    *sqlx.DB
	audit *AuditService
}

func NewCaseService(db *sqlx.DB, audit *AuditService) *CaseService {
	return &CaseService{db: db, audit: audit}
}

type CaseFilter struct {
	Status string
	Type   string
	Search string
	Limit  int
	Offset int
}

type CaseDetail struct {
	models.Case
	Parties        []models.Party     `json:"parties"`
	RecentEvents   []models.CaseEvent `json:"recent_events"`
	DeadlinesCount int                `json:"deadlines_count"`
}

type CreateCaseInput struct {
	CaseNumber string  `json:"case_number"`
	Title      string  `json:"title"`
	CaseType   *string `json:"case_type,omitempty"`
	Court      *string `json:"court,omitempty"`
	CourtRef   *string `json:"court_ref,omitempty"`
	Status     string  `json:"status"`
}

type UpdateCaseInput struct {
	CaseNumber *string `json:"case_number,omitempty"`
	Title      *string `json:"title,omitempty"`
	CaseType   *string `json:"case_type,omitempty"`
	Court      *string `json:"court,omitempty"`
	CourtRef   *string `json:"court_ref,omitempty"`
	Status     *string `json:"status,omitempty"`
}

func (s *CaseService) List(ctx context.Context, tenantID uuid.UUID, filter CaseFilter) ([]models.Case, int, error) {
	if filter.Limit <= 0 {
		filter.Limit = 20
	}
	if filter.Limit > 100 {
		filter.Limit = 100
	}

	// Build WHERE clause
	where := "WHERE tenant_id = $1"
	args := []interface{}{tenantID}
	argIdx := 2

	if filter.Status != "" {
		where += fmt.Sprintf(" AND status = $%d", argIdx)
		args = append(args, filter.Status)
		argIdx++
	}
	if filter.Type != "" {
		where += fmt.Sprintf(" AND case_type = $%d", argIdx)
		args = append(args, filter.Type)
		argIdx++
	}
	if filter.Search != "" {
		where += fmt.Sprintf(" AND (title ILIKE $%d OR case_number ILIKE $%d)", argIdx, argIdx)
		args = append(args, "%"+filter.Search+"%")
		argIdx++
	}

	// Count total
	var total int
	countQuery := "SELECT COUNT(*) FROM cases " + where
	if err := s.db.GetContext(ctx, &total, countQuery, args...); err != nil {
		return nil, 0, fmt.Errorf("counting cases: %w", err)
	}

	// Fetch page
	query := fmt.Sprintf("SELECT * FROM cases %s ORDER BY updated_at DESC LIMIT $%d OFFSET $%d",
		where, argIdx, argIdx+1)
	args = append(args, filter.Limit, filter.Offset)

	var cases []models.Case
	if err := s.db.SelectContext(ctx, &cases, query, args...); err != nil {
		return nil, 0, fmt.Errorf("listing cases: %w", err)
	}

	return cases, total, nil
}

func (s *CaseService) GetByID(ctx context.Context, tenantID, caseID uuid.UUID) (*CaseDetail, error) {
	var c models.Case
	err := s.db.GetContext(ctx, &c,
		"SELECT * FROM cases WHERE id = $1 AND tenant_id = $2", caseID, tenantID)
	if err != nil {
		if err == sql.ErrNoRows {
			return nil, nil
		}
		return nil, fmt.Errorf("getting case: %w", err)
	}

	detail := &CaseDetail{Case: c}

	// Parties
	if err := s.db.SelectContext(ctx, &detail.Parties,
		"SELECT * FROM parties WHERE case_id = $1 AND tenant_id = $2 ORDER BY name",
		caseID, tenantID); err != nil {
		return nil, fmt.Errorf("getting parties: %w", err)
	}

	// Recent events (last 20)
	if err := s.db.SelectContext(ctx, &detail.RecentEvents,
		"SELECT * FROM case_events WHERE case_id = $1 AND tenant_id = $2 ORDER BY created_at DESC LIMIT 20",
		caseID, tenantID); err != nil {
		return nil, fmt.Errorf("getting events: %w", err)
	}

	// Deadlines count
	if err := s.db.GetContext(ctx, &detail.DeadlinesCount,
		"SELECT COUNT(*) FROM deadlines WHERE case_id = $1 AND tenant_id = $2",
		caseID, tenantID); err != nil {
		return nil, fmt.Errorf("counting deadlines: %w", err)
	}

	return detail, nil
}

func (s *CaseService) Create(ctx context.Context, tenantID uuid.UUID, userID uuid.UUID, input CreateCaseInput) (*models.Case, error) {
	if input.Status == "" {
		input.Status = "active"
	}

	id := uuid.New()
	now := time.Now()

	_, err := s.db.ExecContext(ctx,
		`INSERT INTO cases (id, tenant_id, case_number, title, case_type, court, court_ref, status, metadata, created_at, updated_at)
		 VALUES ($1, $2, $3, $4, $5, $6, $7, $8, '{}', $9, $9)`,
		id, tenantID, input.CaseNumber, input.Title, input.CaseType, input.Court, input.CourtRef, input.Status, now)
	if err != nil {
		return nil, fmt.Errorf("creating case: %w", err)
	}

	// Create case_created event
	createEvent(ctx, s.db, tenantID, id, userID, "case_created", "Case created", nil)

	var c models.Case
	if err := s.db.GetContext(ctx, &c, "SELECT * FROM cases WHERE id = $1", id); err != nil {
		return nil, fmt.Errorf("fetching created case: %w", err)
	}

	s.audit.Log(ctx, "create", "case", &id, nil, c)

	return &c, nil
}

func (s *CaseService) Update(ctx context.Context, tenantID, caseID uuid.UUID, userID uuid.UUID, input UpdateCaseInput) (*models.Case, error) {
	// Fetch current to detect status change
	var current models.Case
	err := s.db.GetContext(ctx, &current,
		"SELECT * FROM cases WHERE id = $1 AND tenant_id = $2", caseID, tenantID)
	if err != nil {
		if err == sql.ErrNoRows {
			return nil, nil
		}
		return nil, fmt.Errorf("fetching case for update: %w", err)
	}

	// Build SET clause dynamically
	sets := []string{}
	args := []interface{}{}
	argIdx := 1

	if input.CaseNumber != nil {
		sets = append(sets, fmt.Sprintf("case_number = $%d", argIdx))
		args = append(args, *input.CaseNumber)
		argIdx++
	}
	if input.Title != nil {
		sets = append(sets, fmt.Sprintf("title = $%d", argIdx))
		args = append(args, *input.Title)
		argIdx++
	}
	if input.CaseType != nil {
		sets = append(sets, fmt.Sprintf("case_type = $%d", argIdx))
		args = append(args, *input.CaseType)
		argIdx++
	}
	if input.Court != nil {
		sets = append(sets, fmt.Sprintf("court = $%d", argIdx))
		args = append(args, *input.Court)
		argIdx++
	}
	if input.CourtRef != nil {
		sets = append(sets, fmt.Sprintf("court_ref = $%d", argIdx))
		args = append(args, *input.CourtRef)
		argIdx++
	}
	if input.Status != nil {
		sets = append(sets, fmt.Sprintf("status = $%d", argIdx))
		args = append(args, *input.Status)
		argIdx++
	}

	if len(sets) == 0 {
		return &current, nil
	}

	sets = append(sets, fmt.Sprintf("updated_at = $%d", argIdx))
	args = append(args, time.Now())
	argIdx++

	query := fmt.Sprintf("UPDATE cases SET %s WHERE id = $%d AND tenant_id = $%d",
		joinStrings(sets, ", "), argIdx, argIdx+1)
	args = append(args, caseID, tenantID)

	if _, err := s.db.ExecContext(ctx, query, args...); err != nil {
		return nil, fmt.Errorf("updating case: %w", err)
	}

	// Log status change event
	if input.Status != nil && *input.Status != current.Status {
		desc := fmt.Sprintf("Status changed from %s to %s", current.Status, *input.Status)
		createEvent(ctx, s.db, tenantID, caseID, userID, "status_changed", desc, nil)
	}

	var updated models.Case
	if err := s.db.GetContext(ctx, &updated, "SELECT * FROM cases WHERE id = $1", caseID); err != nil {
		return nil, fmt.Errorf("fetching updated case: %w", err)
	}

	s.audit.Log(ctx, "update", "case", &caseID, current, updated)

	return &updated, nil
}

func (s *CaseService) Delete(ctx context.Context, tenantID, caseID uuid.UUID, userID uuid.UUID) error {
	result, err := s.db.ExecContext(ctx,
		"UPDATE cases SET status = 'archived', updated_at = $1 WHERE id = $2 AND tenant_id = $3 AND status != 'archived'",
		time.Now(), caseID, tenantID)
	if err != nil {
		return fmt.Errorf("archiving case: %w", err)
	}
	rows, _ := result.RowsAffected()
	if rows == 0 {
		return sql.ErrNoRows
	}
	createEvent(ctx, s.db, tenantID, caseID, userID, "case_archived", "Case archived", nil)
	s.audit.Log(ctx, "delete", "case", &caseID, map[string]string{"status": "active"}, map[string]string{"status": "archived"})
	return nil
}

func createEvent(ctx context.Context, db *sqlx.DB, tenantID, caseID uuid.UUID, userID uuid.UUID, eventType, title string, description *string) {
	now := time.Now()
	db.ExecContext(ctx,
		`INSERT INTO case_events (id, tenant_id, case_id, event_type, title, description, event_date, created_by, metadata, created_at, updated_at)
		 VALUES ($1, $2, $3, $4, $5, $6, $7, $8, '{}', $7, $7)`,
		uuid.New(), tenantID, caseID, eventType, title, description, now, userID)
}

func joinStrings(strs []string, sep string) string {
	result := ""
	for i, s := range strs {
		if i > 0 {
			result += sep
		}
		result += s
	}
	return result
}
