package services

import (
	"errors"
	"fmt"
	"io"
	"mime/multipart"
	"net/http"
	"os"
	"path/filepath"
	"time"

	"mohportal/db"
	"mohportal/models"

	"github.com/google/uuid"
	"golang.org/x/crypto/bcrypt"
	"gorm.io/gorm"
)

// TenantService handles tenant-related operations
type TenantService struct{}

// CreateTenant creates a new tenant
func (ts *TenantService) CreateTenant(name, slug, description, logoURL string) (*models.Tenant, error) {
	tenant := &models.Tenant{
		ID:          uuid.New(),
		Name:        name,
		Slug:        slug,
		Description: description,
		LogoURL:     logoURL,
		CreatedAt:   time.Now(),
		UpdatedAt:   time.Now(),
		IsActive:    true,
	}

	if err := db.DB.Create(tenant).Error; err != nil {
		return nil, err
	}

	return tenant, nil
}

// GetTenant retrieves a tenant by ID
func (ts *TenantService) GetTenant(id uuid.UUID) (*models.Tenant, error) {
	var tenant models.Tenant
	if err := db.DB.First(&tenant, "id = ?", id).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, errors.New("tenant not found")
		}
		return nil, err
	}
	return &tenant, nil
}

// GetTenantBySlug retrieves a tenant by slug
func (ts *TenantService) GetTenantBySlug(slug string) (*models.Tenant, error) {
	var tenant models.Tenant
	if err := db.DB.First(&tenant, "slug = ?", slug).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, errors.New("tenant not found")
		}
		return nil, err
	}
	return &tenant, nil
}

// GetTenants retrieves all tenants (with optional filtering)
func (ts *TenantService) GetTenants(limit, offset int) ([]*models.Tenant, error) {
	var tenants []*models.Tenant
	query := db.DB.Limit(limit).Offset(offset)

	if err := query.Find(&tenants).Error; err != nil {
		return nil, err
	}

	return tenants, nil
}

// UpdateTenant updates an existing tenant
func (ts *TenantService) UpdateTenant(id uuid.UUID, name, slug, description, logoURL string) (*models.Tenant, error) {
	var tenant models.Tenant
	if err := db.DB.First(&tenant, "id = ?", id).Error; err != nil {
		return nil, err
	}

	tenant.Name = name
	tenant.Slug = slug
	tenant.Description = description
	tenant.LogoURL = logoURL
	tenant.UpdatedAt = time.Now()

	if err := db.DB.Save(&tenant).Error; err != nil {
		return nil, err
	}

	return &tenant, nil
}

// DeleteTenant deletes a tenant
func (ts *TenantService) DeleteTenant(id uuid.UUID) error {
	return db.DB.Delete(&models.Tenant{}, "id = ?", id).Error
}

// UserService handles user-related operations
type UserService struct{}

// CreateUser creates a new user
func (us *UserService) CreateUser(tenantID uuid.UUID, email, username, firstName, lastName, phone, role, password string) (*models.User, error) {
	// Check if user already exists
	var existingUser models.User
	if err := db.DB.Where("email = ? OR username = ?", email, username).First(&existingUser).Error; err == nil {
		return nil, errors.New("user with email or username already exists")
	}

	// Hash the password
	hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
	if err != nil {
		return nil, err
	}

	user := &models.User{
		ID:       uuid.New(),
		TenantID: tenantID,
		Email:    email,
		Username: username,
		FirstName: firstName,
		LastName:  lastName,
		Phone:     phone,
		Role:      role,
		PasswordHash: string(hashedPassword),
		CreatedAt: time.Now(),
		UpdatedAt: time.Now(),
		IsActive:  true,
	}

	if err := db.DB.Create(user).Error; err != nil {
		return nil, err
	}

	return user, nil
}

// GetUser retrieves a user by ID
func (us *UserService) GetUser(id uuid.UUID) (*models.User, error) {
	var user models.User
	if err := db.DB.Preload("Tenant").First(&user, "id = ?", id).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, errors.New("user not found")
		}
		return nil, err
	}
	return &user, nil
}

// GetUserByEmail retrieves a user by email
func (us *UserService) GetUserByEmail(email string) (*models.User, error) {
	var user models.User
	if err := db.DB.Preload("Tenant").First(&user, "email = ?", email).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, errors.New("user not found")
		}
		return nil, err
	}
	return &user, nil
}

// AuthenticateUser authenticates a user by email and password
func (us *UserService) AuthenticateUser(email, password string) (*models.User, error) {
	var user models.User
	if err := db.DB.First(&user, "email = ?", email).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			// Return error without specifying whether email exists to prevent timing attacks
			return nil, errors.New("invalid credentials")
		}
		return nil, err
	}

	if !user.IsActive {
		return nil, errors.New("user account is deactivated")
	}

	// Compare the password
	if err := bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(password)); err != nil {
		return nil, errors.New("invalid credentials")
	}

	// Update last login
	loginTime := time.Now()
	user.LastLogin = &loginTime
	db.DB.Save(&user)

	return &user, nil
}

// UpdateUser updates an existing user
func (us *UserService) UpdateUser(id uuid.UUID, email, username, firstName, lastName, phone, role string) (*models.User, error) {
	var user models.User
	if err := db.DB.First(&user, "id = ?", id).Error; err != nil {
		return nil, err
	}

	// Check if email or username is already taken by another user
	var existingUser models.User
	if err := db.DB.Where("email = ? AND id != ?", email, id).First(&existingUser).Error; err == nil {
		return nil, errors.New("email already in use by another user")
	}

	if username != "" {
		if err := db.DB.Where("username = ? AND id != ?", username, id).First(&existingUser).Error; err == nil {
			return nil, errors.New("username already in use by another user")
		}
	}

	user.Email = email
	if username != "" {
		user.Username = username
	}
	user.FirstName = firstName
	user.LastName = lastName
	user.Phone = phone
	user.Role = role
	user.UpdatedAt = time.Now()

	if err := db.DB.Save(&user).Error; err != nil {
		return nil, err
	}

	return &user, nil
}

// PositionService handles job position-related operations
type PositionService struct{}

// CreatePosition creates a new job position
func (ps *PositionService) CreatePosition(tenantID, userID uuid.UUID, title, description, requirements, location, employmentType, experienceLevel string, salaryMin, salaryMax *float64) (*models.JobPosition, error) {
	position := &models.JobPosition{
		ID:              uuid.New(),
		TenantID:        tenantID,
		UserID:          userID,
		Title:           title,
		Description:     description,
		Requirements:    requirements,
		Location:        location,
		EmploymentType:  employmentType,
		SalaryMin:       salaryMin,
		SalaryMax:       salaryMax,
		ExperienceLevel: experienceLevel,
		PostedAt:        time.Now(),
		Status:          "open",
		CreatedAt:       time.Now(),
		UpdatedAt:       time.Now(),
		IsActive:        true,
	}

	if err := db.DB.Create(position).Error; err != nil {
		return nil, err
	}

	return position, nil
}

// GetPosition retrieves a position by ID
func (ps *PositionService) GetPosition(id uuid.UUID) (*models.JobPosition, error) {
	var position models.JobPosition
	if err := db.DB.Preload("User").First(&position, "id = ?", id).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, errors.New("position not found")
		}
		return nil, err
	}
	return &position, nil
}

// GetPositions retrieves positions with optional filtering
func (ps *PositionService) GetPositions(tenantID *uuid.UUID, limit, offset int, status, employmentType, experienceLevel, location string) ([]*models.JobPosition, error) {
	var positions []*models.JobPosition
	query := db.DB.Preload("User").Limit(limit).Offset(offset)

	if tenantID != nil {
		query = query.Where("tenant_id = ?", tenantID)
	}

	if status != "" {
		query = query.Where("status = ?", status)
	}

	if employmentType != "" {
		query = query.Where("employment_type = ?", employmentType)
	}

	if experienceLevel != "" {
		query = query.Where("experience_level = ?", experienceLevel)
	}

	if location != "" {
		query = query.Where("location LIKE ?", "%"+location+"%")
	}

	query = query.Where("is_active = ? AND status = ?", true, "open")

	if err := query.Find(&positions).Error; err != nil {
		return nil, err
	}

	return positions, nil
}

// UpdatePosition updates an existing position
func (ps *PositionService) UpdatePosition(id uuid.UUID, title, description, requirements, location, employmentType, experienceLevel string, salaryMin, salaryMax *float64) (*models.JobPosition, error) {
	var position models.JobPosition
	if err := db.DB.First(&position, "id = ?", id).Error; err != nil {
		return nil, err
	}

	position.Title = title
	position.Description = description
	position.Requirements = requirements
	position.Location = location
	position.EmploymentType = employmentType
	position.ExperienceLevel = experienceLevel
	position.SalaryMin = salaryMin
	position.SalaryMax = salaryMax
	position.UpdatedAt = time.Now()

	if err := db.DB.Save(&position).Error; err != nil {
		return nil, err
	}

	return &position, nil
}

// ClosePosition closes a position (marks as filled or cancelled)
func (ps *PositionService) ClosePosition(id uuid.UUID, status string) (*models.JobPosition, error) {
	var position models.JobPosition
	if err := db.DB.First(&position, "id = ?", id).Error; err != nil {
		return nil, err
	}

	if status != "closed" && status != "filled" {
		return nil, errors.New("invalid status for closing position")
	}

	position.Status = status
	closedTime := time.Now()
	position.ClosedAt = &closedTime
	position.UpdatedAt = time.Now()

	if err := db.DB.Save(&position).Error; err != nil {
		return nil, err
	}

	return &position, nil
}

// DeletePosition deletes a position
func (ps *PositionService) DeletePosition(id uuid.UUID) error {
	return db.DB.Delete(&models.JobPosition{}, "id = ?", id).Error
}

// ResumeService handles resume-related operations
type ResumeService struct{}

// UploadResume uploads a resume file and creates a record
func (rs *ResumeService) UploadResume(userID uuid.UUID, fileHeader *multipart.FileHeader, title string) (*models.Resume, error) {
	// Validate file type
	allowedTypes := map[string]bool{
		"application/pdf":               true,
		"application/msword":            true,
		"application/vnd.openxmlformats-officedocument.wordprocessingml.document": true,
		"text/plain": true,
	}
	
	file, err := fileHeader.Open()
	if err != nil {
		return nil, err
	}
	defer file.Close()

	// Detect content type
	buffer := make([]byte, 512)
	_, err = file.Read(buffer)
	if err != nil {
		return nil, err
	}
	
	fileType := http.DetectContentType(buffer)
	if !allowedTypes[fileType] {
		return nil, errors.New("file type not allowed")
	}

	// Ensure the static/uploads directory exists
	uploadDir := "./static/uploads/resumes"
	if err := os.MkdirAll(uploadDir, 0755); err != nil {
		return nil, err
	}

	// Generate unique filename
	filename := fmt.Sprintf("%s_%s", userID.String(), fileHeader.Filename)
	uploadPath := filepath.Join(uploadDir, filename)

	// Copy file to upload directory
	src, err := fileHeader.Open()
	if err != nil {
		return nil, err
	}
	defer src.Close()

	dst, err := os.Create(uploadPath)
	if err != nil {
		return nil, err
	}
	defer dst.Close()

	if _, err := io.Copy(dst, src); err != nil {
		return nil, err
	}

	// Create resume record
	resume := &models.Resume{
		ID:        uuid.New(),
		UserID:    userID,
		Title:     title,
		FilePath:  uploadPath,
		FileType:  fileType,
		FileSize:  fileHeader.Size,
		CreatedAt: time.Now(),
		UpdatedAt: time.Now(),
		IsActive:  true,
	}

	if err := db.DB.Create(resume).Error; err != nil {
		// Clean up the uploaded file if DB operation fails
		os.Remove(uploadPath)
		return nil, err
	}

	return resume, nil
}

// GetResume retrieves a resume by ID
func (rs *ResumeService) GetResume(id uuid.UUID) (*models.Resume, error) {
	var resume models.Resume
	if err := db.DB.First(&resume, "id = ?", id).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, errors.New("resume not found")
		}
		return nil, err
	}
	return &resume, nil
}

// GetResumeByUser retrieves all resumes for a user
func (rs *ResumeService) GetResumeByUser(userID uuid.UUID) ([]*models.Resume, error) {
	var resumes []*models.Resume
	if err := db.DB.Where("user_id = ?", userID).Find(&resumes).Error; err != nil {
		return nil, err
	}
	return resumes, nil
}

// ApplicationService handles job application-related operations
type ApplicationService struct{}

// CreateApplication creates a new job application
func (as *ApplicationService) CreateApplication(positionID, userID uuid.UUID, resumeID *uuid.UUID, coverLetter string) (*models.Application, error) {
	// Check if user already applied for this position
	var existingApplication models.Application
	if err := db.DB.Where("position_id = ? AND user_id = ?", positionID, userID).First(&existingApplication).Error; err == nil {
		return nil, errors.New("user has already applied for this position")
	}

	// Verify position exists and is open
	var position models.JobPosition
	if err := db.DB.First(&position, "id = ? AND status = ? AND is_active = ?", positionID, "open", true).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, errors.New("position not found or not open")
		}
		return nil, err
	}

	application := &models.Application{
		ID:          uuid.New(),
		PositionID:  positionID,
		UserID:      userID,
		ResumeID:    resumeID,
		CoverLetter: coverLetter,
		Status:      "pending",
		AppliedAt:   time.Now(),
		CreatedAt:   time.Now(),
		UpdatedAt:   time.Now(),
	}

	if err := db.DB.Create(application).Error; err != nil {
		return nil, err
	}

	return application, nil
}

// GetApplication retrieves an application by ID
func (as *ApplicationService) GetApplication(id uuid.UUID) (*models.Application, error) {
	var application models.Application
	if err := db.DB.Preload("User").Preload("Position").Preload("Resume").First(&application, "id = ?", id).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, errors.New("application not found")
		}
		return nil, err
	}
	return &application, nil
}

// GetApplications retrieves applications with optional filtering
func (as *ApplicationService) GetApplications(userID, positionID *uuid.UUID, status string, limit, offset int) ([]*models.Application, error) {
	var applications []*models.Application
	query := db.DB.Preload("User").Preload("Position").Preload("Resume").Limit(limit).Offset(offset)

	if userID != nil {
		query = query.Where("user_id = ?", userID)
	}

	if positionID != nil {
		query = query.Where("position_id = ?", positionID)
	}

	if status != "" {
		query = query.Where("status = ?", status)
	}

	if err := query.Find(&applications).Error; err != nil {
		return nil, err
	}

	return applications, nil
}

// UpdateApplication updates an application status
func (as *ApplicationService) UpdateApplication(id uuid.UUID, status string, reviewerID uuid.UUID, notes string) (*models.Application, error) {
	var application models.Application
	if err := db.DB.First(&application, "id = ?", id).Error; err != nil {
		return nil, err
	}

	// Validate status
	if !models.ValidStatus(status) {
		return nil, errors.New("invalid status")
	}

	application.Status = status
	application.ReviewerUserID = &reviewerID
	application.Notes = notes
	application.UpdatedAt = time.Now()
	reviewedTime := time.Now()
	application.ReviewedAt = &reviewedTime

	if err := db.DB.Save(&application).Error; err != nil {
		return nil, err
	}

	return &application, nil
}

// DeleteApplication deletes an application
func (as *ApplicationService) DeleteApplication(id uuid.UUID) error {
	return db.DB.Delete(&models.Application{}, "id = ?", id).Error
}

// GetApplicationsForPosition retrieves all applications for a specific position
func (as *ApplicationService) GetApplicationsForPosition(positionID uuid.UUID, status string, limit, offset int) ([]*models.Application, error) {
	var applications []*models.Application
	query := db.DB.Preload("User").Preload("Resume").Limit(limit).Offset(offset).Where("position_id = ?", positionID)

	if status != "" {
		query = query.Where("status = ?", status)
	}

	if err := query.Find(&applications).Error; err != nil {
		return nil, err
	}

	return applications, nil
}