package models

import (
	"context"
	"errors"
	"fmt"

	"github.com/timetracker/backend/internal/db"
	"github.com/timetracker/backend/internal/types"
	"gorm.io/gorm"
)

// Project represents a project in the system
type Project struct {
	EntityBase
	Name       string      `gorm:"column:name;not null"`
	CustomerID *types.ULID `gorm:"column:customer_id;type:bytea;index"`

	// Relationships (for Eager Loading)
	Customer *Customer `gorm:"foreignKey:CustomerID"`
}

// TableName specifies the table name for GORM
func (Project) TableName() string {
	return "projects"
}

// ProjectCreate contains the fields for creating a new project
type ProjectCreate struct {
	Name       string
	CustomerID *types.ULID
}

// ProjectUpdate contains the updatable fields of a project
type ProjectUpdate struct {
	ID         types.ULID                 `gorm:"-"` // Exclude from updates
	Name       *string                    `gorm:"column:name"`
	CustomerID types.Nullable[types.ULID] `gorm:"column:customer_id"`
}

// Validate checks if the Create struct contains valid data
func (pc *ProjectCreate) Validate() error {
	if pc.Name == "" {
		return errors.New("project name cannot be empty")
	}
	// Check for valid CustomerID
	if pc.CustomerID != nil && pc.CustomerID.Compare(types.ULID{}) == 0 {
		return errors.New("customerID cannot be empty")
	}
	return nil
}

// Validate checks if the Update struct contains valid data
func (pu *ProjectUpdate) Validate() error {
	if pu.Name != nil && *pu.Name == "" {
		return errors.New("project name cannot be empty")
	}
	return nil
}

// GetProjectByID finds a project by its ID
func GetProjectByID(ctx context.Context, id types.ULID) (*Project, error) {
	var project Project
	result := db.GetEngine(ctx).Where("id = ?", id).First(&project)
	if result.Error != nil {
		if errors.Is(result.Error, gorm.ErrRecordNotFound) {
			return nil, nil
		}
		return nil, result.Error
	}
	return &project, nil
}

// GetProjectWithCustomer loads a project with the associated customer information
func GetProjectWithCustomer(ctx context.Context, id types.ULID) (*Project, error) {
	var project Project
	result := db.GetEngine(ctx).Preload("Customer").Where("id = ?", id).First(&project)
	if result.Error != nil {
		if errors.Is(result.Error, gorm.ErrRecordNotFound) {
			return nil, nil
		}
		return nil, result.Error
	}
	return &project, nil
}

// GetAllProjects returns all projects
func GetAllProjects(ctx context.Context) ([]Project, error) {
	var projects []Project
	result := db.GetEngine(ctx).Find(&projects)
	if result.Error != nil {
		return nil, result.Error
	}
	return projects, nil
}

// GetAllProjectsWithCustomers returns all projects with customer information
func GetAllProjectsWithCustomers(ctx context.Context) ([]Project, error) {
	var projects []Project
	result := db.GetEngine(ctx).Preload("Customer").Find(&projects)
	if result.Error != nil {
		return nil, result.Error
	}
	return projects, nil
}

// GetProjectsByCustomerID returns all projects of a specific customer
func GetProjectsByCustomerID(ctx context.Context, customerId types.ULID) ([]Project, error) {
	var projects []Project
	result := db.GetEngine(ctx).Where("customer_id = ?", customerId.ULID).Find(&projects)
	if result.Error != nil {
		return nil, result.Error
	}
	return projects, nil
}

// CreateProject creates a new project with validation
func CreateProject(ctx context.Context, create ProjectCreate) (*Project, error) {
	// Validation
	if err := create.Validate(); err != nil {
		return nil, fmt.Errorf("validation error: %w", err)
	}

	// Check if the customer exists
	if create.CustomerID != nil {
		customer, err := GetCustomerByID(ctx, *create.CustomerID)
		if err != nil {
			return nil, fmt.Errorf("error checking the customer: %w", err)
		}
		if customer == nil {
			return nil, errors.New("the specified customer does not exist")
		}
	}

	project := Project{
		Name:       create.Name,
		CustomerID: create.CustomerID,
	}

	result := db.GetEngine(ctx).Create(&project)
	if result.Error != nil {
		return nil, fmt.Errorf("error creating the project: %w", result.Error)
	}
	return &project, nil
}

// UpdateProject updates an existing project with validation
func UpdateProject(ctx context.Context, update ProjectUpdate) (*Project, error) {
	// Validation
	if err := update.Validate(); err != nil {
		return nil, fmt.Errorf("validation error: %w", err)
	}

	project, err := GetProjectByID(ctx, update.ID)
	if err != nil {
		return nil, err
	}
	if project == nil {
		return nil, errors.New("project not found")
	}

	// If CustomerID is updated, check if the customer exists
	if update.CustomerID.Valid {
		if update.CustomerID.Value != nil {
			customer, err := GetCustomerByID(ctx, *update.CustomerID.Value)
			if err != nil {
				return nil, fmt.Errorf("error checking the customer: %w", err)
			}
			if customer == nil {
				return nil, errors.New("the specified customer does not exist")
			}
		} else {
			// If CustomerID is nil, set it to nil in the project
			project.CustomerID = nil
		}
	}

	// Use generic update function
	if err := UpdateModel(ctx, project, update); err != nil {
		return nil, fmt.Errorf("error updating the project: %w", err)
	}

	// Load updated data from the database
	return GetProjectByID(ctx, update.ID)
}

// DeleteProject deletes a project by its ID
func DeleteProject(ctx context.Context, id types.ULID) error {
	// Here you could check if dependent entities exist
	result := db.GetEngine(ctx).Delete(&Project{}, id)
	if result.Error != nil {
		return fmt.Errorf("error deleting the project: %w", result.Error)
	}
	return nil
}

// CreateProjectWithTransaction creates a project within a transaction
func CreateProjectWithTransaction(ctx context.Context, create ProjectCreate) (*Project, error) {
	// Validation
	if err := create.Validate(); err != nil {
		return nil, fmt.Errorf("validation error: %w", err)
	}

	var project *Project

	// Start transaction
	err := db.GetEngine(ctx).Transaction(func(tx *gorm.DB) error {
		// Customer check within the transaction
		var customer Customer
		if err := tx.Where("id = ?", create.CustomerID).First(&customer).Error; err != nil {
			if errors.Is(err, gorm.ErrRecordNotFound) {
				return errors.New("the specified customer does not exist")
			}
			return err
		}

		// Create project
		newProject := Project{
			Name:       create.Name,
			CustomerID: create.CustomerID,
		}

		if err := tx.Create(&newProject).Error; err != nil {
			return err
		}

		// Save project for return
		project = &newProject

		return nil
	})

	if err != nil {
		return nil, fmt.Errorf("transaction error: %w", err)
	}

	return project, nil
}
