package funding

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

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

// PostgresStore implements Store using PostgreSQL
type PostgresStore struct {
	pool *pgxpool.Pool
}

// NewPostgresStore creates a new PostgreSQL store
func NewPostgresStore(pool *pgxpool.Pool) *PostgresStore {
	return &PostgresStore{pool: pool}
}

// CreateApplication creates a new funding application
func (s *PostgresStore) CreateApplication(ctx context.Context, app *FundingApplication) error {
	app.ID = uuid.New()
	app.CreatedAt = time.Now()
	app.UpdatedAt = time.Now()
	app.TotalSteps = 8 // Default 8-step wizard

	// Generate application number
	app.ApplicationNumber = s.generateApplicationNumber(app.FundingProgram, app.SchoolProfile)

	// Marshal JSON fields
	wizardDataJSON, err := json.Marshal(app.WizardData)
	if err != nil {
		return fmt.Errorf("failed to marshal wizard data: %w", err)
	}

	schoolProfileJSON, err := json.Marshal(app.SchoolProfile)
	if err != nil {
		return fmt.Errorf("failed to marshal school profile: %w", err)
	}

	projectPlanJSON, err := json.Marshal(app.ProjectPlan)
	if err != nil {
		return fmt.Errorf("failed to marshal project plan: %w", err)
	}

	budgetJSON, err := json.Marshal(app.Budget)
	if err != nil {
		return fmt.Errorf("failed to marshal budget: %w", err)
	}

	timelineJSON, err := json.Marshal(app.Timeline)
	if err != nil {
		return fmt.Errorf("failed to marshal timeline: %w", err)
	}

	query := `
		INSERT INTO funding_applications (
			id, tenant_id, application_number, title, funding_program, status,
			current_step, total_steps, wizard_data,
			school_profile, project_plan, budget, timeline,
			requested_amount, own_contribution,
			created_at, updated_at, created_by, updated_by
		) VALUES (
			$1, $2, $3, $4, $5, $6,
			$7, $8, $9,
			$10, $11, $12, $13,
			$14, $15,
			$16, $17, $18, $19
		)
	`

	_, err = s.pool.Exec(ctx, query,
		app.ID, app.TenantID, app.ApplicationNumber, app.Title, app.FundingProgram, app.Status,
		app.CurrentStep, app.TotalSteps, wizardDataJSON,
		schoolProfileJSON, projectPlanJSON, budgetJSON, timelineJSON,
		app.RequestedAmount, app.OwnContribution,
		app.CreatedAt, app.UpdatedAt, app.CreatedBy, app.UpdatedBy,
	)

	if err != nil {
		return fmt.Errorf("failed to create application: %w", err)
	}

	return nil
}

// GetApplication retrieves an application by ID
func (s *PostgresStore) GetApplication(ctx context.Context, id uuid.UUID) (*FundingApplication, error) {
	query := `
		SELECT
			id, tenant_id, application_number, title, funding_program, status,
			current_step, total_steps, wizard_data,
			school_profile, project_plan, budget, timeline,
			requested_amount, own_contribution, approved_amount,
			created_at, updated_at, submitted_at, created_by, updated_by
		FROM funding_applications
		WHERE id = $1
	`

	var app FundingApplication
	var wizardDataJSON, schoolProfileJSON, projectPlanJSON, budgetJSON, timelineJSON []byte

	err := s.pool.QueryRow(ctx, query, id).Scan(
		&app.ID, &app.TenantID, &app.ApplicationNumber, &app.Title, &app.FundingProgram, &app.Status,
		&app.CurrentStep, &app.TotalSteps, &wizardDataJSON,
		&schoolProfileJSON, &projectPlanJSON, &budgetJSON, &timelineJSON,
		&app.RequestedAmount, &app.OwnContribution, &app.ApprovedAmount,
		&app.CreatedAt, &app.UpdatedAt, &app.SubmittedAt, &app.CreatedBy, &app.UpdatedBy,
	)

	if err != nil {
		if errors.Is(err, pgx.ErrNoRows) {
			return nil, fmt.Errorf("application not found: %s", id)
		}
		return nil, fmt.Errorf("failed to get application: %w", err)
	}

	// Unmarshal JSON fields
	if len(wizardDataJSON) > 0 {
		if err := json.Unmarshal(wizardDataJSON, &app.WizardData); err != nil {
			return nil, fmt.Errorf("failed to unmarshal wizard data: %w", err)
		}
	}

	if len(schoolProfileJSON) > 0 {
		app.SchoolProfile = &SchoolProfile{}
		if err := json.Unmarshal(schoolProfileJSON, app.SchoolProfile); err != nil {
			return nil, fmt.Errorf("failed to unmarshal school profile: %w", err)
		}
	}

	if len(projectPlanJSON) > 0 {
		app.ProjectPlan = &ProjectPlan{}
		if err := json.Unmarshal(projectPlanJSON, app.ProjectPlan); err != nil {
			return nil, fmt.Errorf("failed to unmarshal project plan: %w", err)
		}
	}

	if len(budgetJSON) > 0 {
		app.Budget = &Budget{}
		if err := json.Unmarshal(budgetJSON, app.Budget); err != nil {
			return nil, fmt.Errorf("failed to unmarshal budget: %w", err)
		}
	}

	if len(timelineJSON) > 0 {
		app.Timeline = &ProjectTimeline{}
		if err := json.Unmarshal(timelineJSON, app.Timeline); err != nil {
			return nil, fmt.Errorf("failed to unmarshal timeline: %w", err)
		}
	}

	// Load attachments
	attachments, err := s.GetAttachments(ctx, id)
	if err == nil {
		app.Attachments = attachments
	}

	return &app, nil
}

// GetApplicationByNumber retrieves an application by number
func (s *PostgresStore) GetApplicationByNumber(ctx context.Context, number string) (*FundingApplication, error) {
	query := `SELECT id FROM funding_applications WHERE application_number = $1`

	var id uuid.UUID
	err := s.pool.QueryRow(ctx, query, number).Scan(&id)
	if err != nil {
		if errors.Is(err, pgx.ErrNoRows) {
			return nil, fmt.Errorf("application not found: %s", number)
		}
		return nil, fmt.Errorf("failed to find application by number: %w", err)
	}

	return s.GetApplication(ctx, id)
}

// UpdateApplication updates an existing application
func (s *PostgresStore) UpdateApplication(ctx context.Context, app *FundingApplication) error {
	app.UpdatedAt = time.Now()

	// Marshal JSON fields
	wizardDataJSON, _ := json.Marshal(app.WizardData)
	schoolProfileJSON, _ := json.Marshal(app.SchoolProfile)
	projectPlanJSON, _ := json.Marshal(app.ProjectPlan)
	budgetJSON, _ := json.Marshal(app.Budget)
	timelineJSON, _ := json.Marshal(app.Timeline)

	query := `
		UPDATE funding_applications SET
			title = $2, funding_program = $3, status = $4,
			current_step = $5, wizard_data = $6,
			school_profile = $7, project_plan = $8, budget = $9, timeline = $10,
			requested_amount = $11, own_contribution = $12, approved_amount = $13,
			updated_at = $14, submitted_at = $15, updated_by = $16
		WHERE id = $1
	`

	result, err := s.pool.Exec(ctx, query,
		app.ID, app.Title, app.FundingProgram, app.Status,
		app.CurrentStep, wizardDataJSON,
		schoolProfileJSON, projectPlanJSON, budgetJSON, timelineJSON,
		app.RequestedAmount, app.OwnContribution, app.ApprovedAmount,
		app.UpdatedAt, app.SubmittedAt, app.UpdatedBy,
	)

	if err != nil {
		return fmt.Errorf("failed to update application: %w", err)
	}

	if result.RowsAffected() == 0 {
		return fmt.Errorf("application not found: %s", app.ID)
	}

	return nil
}

// DeleteApplication soft-deletes an application
func (s *PostgresStore) DeleteApplication(ctx context.Context, id uuid.UUID) error {
	query := `UPDATE funding_applications SET status = 'ARCHIVED', updated_at = $2 WHERE id = $1`
	result, err := s.pool.Exec(ctx, query, id, time.Now())
	if err != nil {
		return fmt.Errorf("failed to delete application: %w", err)
	}
	if result.RowsAffected() == 0 {
		return fmt.Errorf("application not found: %s", id)
	}
	return nil
}

// ListApplications returns a paginated list of applications
func (s *PostgresStore) ListApplications(ctx context.Context, tenantID uuid.UUID, filter ApplicationFilter) (*ApplicationListResponse, error) {
	// Build query with filters
	query := `
		SELECT
			id, tenant_id, application_number, title, funding_program, status,
			current_step, total_steps, wizard_data,
			school_profile, project_plan, budget, timeline,
			requested_amount, own_contribution, approved_amount,
			created_at, updated_at, submitted_at, created_by, updated_by
		FROM funding_applications
		WHERE tenant_id = $1 AND status != 'ARCHIVED'
	`
	args := []interface{}{tenantID}
	argIndex := 2

	if filter.Status != nil {
		query += fmt.Sprintf(" AND status = $%d", argIndex)
		args = append(args, *filter.Status)
		argIndex++
	}

	if filter.FundingProgram != nil {
		query += fmt.Sprintf(" AND funding_program = $%d", argIndex)
		args = append(args, *filter.FundingProgram)
		argIndex++
	}

	// Count total
	countQuery := `SELECT COUNT(*) FROM funding_applications WHERE tenant_id = $1 AND status != 'ARCHIVED'`
	var total int
	s.pool.QueryRow(ctx, countQuery, tenantID).Scan(&total)

	// Add sorting and pagination
	sortBy := "created_at"
	if filter.SortBy != "" {
		sortBy = filter.SortBy
	}
	sortOrder := "DESC"
	if filter.SortOrder == "asc" {
		sortOrder = "ASC"
	}
	query += fmt.Sprintf(" ORDER BY %s %s", sortBy, sortOrder)

	if filter.PageSize <= 0 {
		filter.PageSize = 20
	}
	if filter.Page <= 0 {
		filter.Page = 1
	}
	offset := (filter.Page - 1) * filter.PageSize
	query += fmt.Sprintf(" LIMIT %d OFFSET %d", filter.PageSize, offset)

	rows, err := s.pool.Query(ctx, query, args...)
	if err != nil {
		return nil, fmt.Errorf("failed to list applications: %w", err)
	}
	defer rows.Close()

	var apps []FundingApplication
	for rows.Next() {
		var app FundingApplication
		var wizardDataJSON, schoolProfileJSON, projectPlanJSON, budgetJSON, timelineJSON []byte

		err := rows.Scan(
			&app.ID, &app.TenantID, &app.ApplicationNumber, &app.Title, &app.FundingProgram, &app.Status,
			&app.CurrentStep, &app.TotalSteps, &wizardDataJSON,
			&schoolProfileJSON, &projectPlanJSON, &budgetJSON, &timelineJSON,
			&app.RequestedAmount, &app.OwnContribution, &app.ApprovedAmount,
			&app.CreatedAt, &app.UpdatedAt, &app.SubmittedAt, &app.CreatedBy, &app.UpdatedBy,
		)
		if err != nil {
			return nil, fmt.Errorf("failed to scan application: %w", err)
		}

		// Unmarshal JSON fields
		if len(schoolProfileJSON) > 0 {
			app.SchoolProfile = &SchoolProfile{}
			json.Unmarshal(schoolProfileJSON, app.SchoolProfile)
		}

		apps = append(apps, app)
	}

	return &ApplicationListResponse{
		Applications: apps,
		Total:        total,
		Page:         filter.Page,
		PageSize:     filter.PageSize,
	}, nil
}

// SearchApplications searches applications by text
func (s *PostgresStore) SearchApplications(ctx context.Context, tenantID uuid.UUID, query string) ([]FundingApplication, error) {
	searchQuery := `
		SELECT id FROM funding_applications
		WHERE tenant_id = $1
		AND status != 'ARCHIVED'
		AND (
			title ILIKE $2
			OR application_number ILIKE $2
			OR school_profile::text ILIKE $2
		)
		ORDER BY updated_at DESC
		LIMIT 50
	`

	rows, err := s.pool.Query(ctx, searchQuery, tenantID, "%"+query+"%")
	if err != nil {
		return nil, fmt.Errorf("failed to search applications: %w", err)
	}
	defer rows.Close()

	var apps []FundingApplication
	for rows.Next() {
		var id uuid.UUID
		if err := rows.Scan(&id); err != nil {
			continue
		}
		app, err := s.GetApplication(ctx, id)
		if err == nil {
			apps = append(apps, *app)
		}
	}

	return apps, nil
}

// SaveWizardStep saves data for a wizard step
func (s *PostgresStore) SaveWizardStep(ctx context.Context, appID uuid.UUID, step int, data map[string]interface{}) error {
	// Get current wizard data
	app, err := s.GetApplication(ctx, appID)
	if err != nil {
		return err
	}

	// Initialize wizard data if nil
	if app.WizardData == nil {
		app.WizardData = make(map[string]interface{})
	}

	// Merge step data
	stepKey := fmt.Sprintf("step_%d", step)
	app.WizardData[stepKey] = data
	app.CurrentStep = step

	// Update application
	return s.UpdateApplication(ctx, app)
}

// GetWizardProgress returns the wizard progress
func (s *PostgresStore) GetWizardProgress(ctx context.Context, appID uuid.UUID) (*WizardProgress, error) {
	app, err := s.GetApplication(ctx, appID)
	if err != nil {
		return nil, err
	}

	progress := &WizardProgress{
		CurrentStep:    app.CurrentStep,
		TotalSteps:     app.TotalSteps,
		CompletedSteps: []int{},
		FormData:       app.WizardData,
		LastSavedAt:    app.UpdatedAt,
	}

	// Determine completed steps from wizard data
	for i := 1; i <= app.TotalSteps; i++ {
		stepKey := fmt.Sprintf("step_%d", i)
		if _, ok := app.WizardData[stepKey]; ok {
			progress.CompletedSteps = append(progress.CompletedSteps, i)
		}
	}

	return progress, nil
}

// AddAttachment adds an attachment to an application
func (s *PostgresStore) AddAttachment(ctx context.Context, appID uuid.UUID, attachment *Attachment) error {
	attachment.ID = uuid.New()
	attachment.UploadedAt = time.Now()

	query := `
		INSERT INTO funding_attachments (
			id, application_id, file_name, file_type, file_size,
			category, description, storage_path, uploaded_at, uploaded_by
		) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
	`

	_, err := s.pool.Exec(ctx, query,
		attachment.ID, appID, attachment.FileName, attachment.FileType, attachment.FileSize,
		attachment.Category, attachment.Description, attachment.StoragePath,
		attachment.UploadedAt, attachment.UploadedBy,
	)

	return err
}

// GetAttachments returns all attachments for an application
func (s *PostgresStore) GetAttachments(ctx context.Context, appID uuid.UUID) ([]Attachment, error) {
	query := `
		SELECT id, file_name, file_type, file_size, category, description, storage_path, uploaded_at, uploaded_by
		FROM funding_attachments
		WHERE application_id = $1
		ORDER BY uploaded_at DESC
	`

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

	var attachments []Attachment
	for rows.Next() {
		var a Attachment
		err := rows.Scan(&a.ID, &a.FileName, &a.FileType, &a.FileSize, &a.Category, &a.Description, &a.StoragePath, &a.UploadedAt, &a.UploadedBy)
		if err != nil {
			continue
		}
		attachments = append(attachments, a)
	}

	return attachments, nil
}

// DeleteAttachment deletes an attachment
func (s *PostgresStore) DeleteAttachment(ctx context.Context, attachmentID uuid.UUID) error {
	query := `DELETE FROM funding_attachments WHERE id = $1`
	_, err := s.pool.Exec(ctx, query, attachmentID)
	return err
}

// AddHistoryEntry adds an audit trail entry
func (s *PostgresStore) AddHistoryEntry(ctx context.Context, entry *ApplicationHistoryEntry) error {
	entry.ID = uuid.New()
	entry.PerformedAt = time.Now().Format(time.RFC3339)

	oldValuesJSON, _ := json.Marshal(entry.OldValues)
	newValuesJSON, _ := json.Marshal(entry.NewValues)
	changedFieldsJSON, _ := json.Marshal(entry.ChangedFields)

	query := `
		INSERT INTO funding_application_history (
			id, application_id, action, changed_fields, old_values, new_values,
			performed_by, performed_at, notes
		) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
	`

	_, err := s.pool.Exec(ctx, query,
		entry.ID, entry.ApplicationID, entry.Action, changedFieldsJSON, oldValuesJSON, newValuesJSON,
		entry.PerformedBy, entry.PerformedAt, entry.Notes,
	)

	return err
}

// GetHistory returns the audit trail for an application
func (s *PostgresStore) GetHistory(ctx context.Context, appID uuid.UUID) ([]ApplicationHistoryEntry, error) {
	query := `
		SELECT id, application_id, action, changed_fields, old_values, new_values, performed_by, performed_at, notes
		FROM funding_application_history
		WHERE application_id = $1
		ORDER BY performed_at DESC
	`

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

	var history []ApplicationHistoryEntry
	for rows.Next() {
		var entry ApplicationHistoryEntry
		var changedFieldsJSON, oldValuesJSON, newValuesJSON []byte

		err := rows.Scan(
			&entry.ID, &entry.ApplicationID, &entry.Action, &changedFieldsJSON, &oldValuesJSON, &newValuesJSON,
			&entry.PerformedBy, &entry.PerformedAt, &entry.Notes,
		)
		if err != nil {
			continue
		}

		json.Unmarshal(changedFieldsJSON, &entry.ChangedFields)
		json.Unmarshal(oldValuesJSON, &entry.OldValues)
		json.Unmarshal(newValuesJSON, &entry.NewValues)

		history = append(history, entry)
	}

	return history, nil
}

// GetStatistics returns funding statistics
func (s *PostgresStore) GetStatistics(ctx context.Context, tenantID uuid.UUID) (*FundingStatistics, error) {
	stats := &FundingStatistics{
		ByProgram: make(map[FundingProgram]int),
		ByState:   make(map[FederalState]int),
	}

	// Total and by status
	query := `
		SELECT
			COUNT(*) as total,
			COUNT(*) FILTER (WHERE status = 'DRAFT') as draft,
			COUNT(*) FILTER (WHERE status = 'SUBMITTED') as submitted,
			COUNT(*) FILTER (WHERE status = 'APPROVED') as approved,
			COUNT(*) FILTER (WHERE status = 'REJECTED') as rejected,
			COALESCE(SUM(requested_amount), 0) as total_requested,
			COALESCE(SUM(COALESCE(approved_amount, 0)), 0) as total_approved
		FROM funding_applications
		WHERE tenant_id = $1 AND status != 'ARCHIVED'
	`

	err := s.pool.QueryRow(ctx, query, tenantID).Scan(
		&stats.TotalApplications, &stats.DraftCount, &stats.SubmittedCount,
		&stats.ApprovedCount, &stats.RejectedCount,
		&stats.TotalRequested, &stats.TotalApproved,
	)
	if err != nil {
		return nil, err
	}

	// By program
	programQuery := `
		SELECT funding_program, COUNT(*)
		FROM funding_applications
		WHERE tenant_id = $1 AND status != 'ARCHIVED'
		GROUP BY funding_program
	`
	rows, _ := s.pool.Query(ctx, programQuery, tenantID)
	for rows.Next() {
		var program FundingProgram
		var count int
		rows.Scan(&program, &count)
		stats.ByProgram[program] = count
	}
	rows.Close()

	return stats, nil
}

// SaveExportBundle saves an export bundle record
func (s *PostgresStore) SaveExportBundle(ctx context.Context, bundle *ExportBundle) error {
	bundle.ID = uuid.New()
	bundle.GeneratedAt = time.Now()
	bundle.ExpiresAt = time.Now().Add(24 * time.Hour) // 24h expiry

	documentsJSON, _ := json.Marshal(bundle.Documents)

	query := `
		INSERT INTO funding_export_bundles (
			id, application_id, documents, generated_at, download_url, expires_at
		) VALUES ($1, $2, $3, $4, $5, $6)
	`

	_, err := s.pool.Exec(ctx, query,
		bundle.ID, bundle.ApplicationID, documentsJSON,
		bundle.GeneratedAt, bundle.DownloadURL, bundle.ExpiresAt,
	)

	return err
}

// GetExportBundle retrieves an export bundle
func (s *PostgresStore) GetExportBundle(ctx context.Context, bundleID uuid.UUID) (*ExportBundle, error) {
	query := `
		SELECT id, application_id, documents, generated_at, download_url, expires_at
		FROM funding_export_bundles
		WHERE id = $1 AND expires_at > NOW()
	`

	var bundle ExportBundle
	var documentsJSON []byte

	err := s.pool.QueryRow(ctx, query, bundleID).Scan(
		&bundle.ID, &bundle.ApplicationID, &documentsJSON,
		&bundle.GeneratedAt, &bundle.DownloadURL, &bundle.ExpiresAt,
	)
	if err != nil {
		return nil, err
	}

	json.Unmarshal(documentsJSON, &bundle.Documents)

	return &bundle, nil
}

// generateApplicationNumber creates a unique application number
func (s *PostgresStore) generateApplicationNumber(program FundingProgram, school *SchoolProfile) string {
	year := time.Now().Year()
	state := "XX"
	if school != nil {
		state = string(school.FederalState)
	}

	prefix := "FA"
	switch program {
	case FundingProgramDigitalPakt1:
		prefix = "DP1"
	case FundingProgramDigitalPakt2:
		prefix = "DP2"
	case FundingProgramLandesfoerderung:
		prefix = "LF"
	}

	// Get sequence number
	var seq int
	s.pool.QueryRow(context.Background(),
		`SELECT COALESCE(MAX(CAST(SUBSTRING(application_number FROM '\d{5}$') AS INTEGER)), 0) + 1
		 FROM funding_applications WHERE application_number LIKE $1`,
		fmt.Sprintf("%s-%s-%d-%%", prefix, state, year),
	).Scan(&seq)

	return fmt.Sprintf("%s-%s-%d-%05d", prefix, state, year, seq)
}
