package services

import (
	"context"
	"database/sql"
	"encoding/json"
	"fmt"

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

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

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

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

// Create creates a new tenant and assigns the creator as owner.
func (s *TenantService) Create(ctx context.Context, userID uuid.UUID, name, slug string) (*models.Tenant, error) {
	tx, err := s.db.BeginTxx(ctx, nil)
	if err != nil {
		return nil, fmt.Errorf("begin transaction: %w", err)
	}
	defer tx.Rollback()

	var tenant models.Tenant
	err = tx.QueryRowxContext(ctx,
		`INSERT INTO tenants (name, slug) VALUES ($1, $2) RETURNING id, name, slug, settings, created_at, updated_at`,
		name, slug,
	).StructScan(&tenant)
	if err != nil {
		return nil, fmt.Errorf("insert tenant: %w", err)
	}

	_, err = tx.ExecContext(ctx,
		`INSERT INTO user_tenants (user_id, tenant_id, role) VALUES ($1, $2, 'owner')`,
		userID, tenant.ID,
	)
	if err != nil {
		return nil, fmt.Errorf("assign owner: %w", err)
	}

	if err := tx.Commit(); err != nil {
		return nil, fmt.Errorf("commit: %w", err)
	}

	s.audit.Log(ctx, "create", "tenant", &tenant.ID, nil, tenant)
	return &tenant, nil
}

// ListForUser returns all tenants the user belongs to.
func (s *TenantService) ListForUser(ctx context.Context, userID uuid.UUID) ([]models.TenantWithRole, error) {
	var tenants []models.TenantWithRole
	err := s.db.SelectContext(ctx, &tenants,
		`SELECT t.id, t.name, t.slug, t.settings, t.created_at, t.updated_at, ut.role
		 FROM tenants t
		 JOIN user_tenants ut ON ut.tenant_id = t.id
		 WHERE ut.user_id = $1
		 ORDER BY t.name`,
		userID,
	)
	if err != nil {
		return nil, fmt.Errorf("list tenants: %w", err)
	}
	return tenants, nil
}

// GetByID returns a single tenant. The caller must verify the user has access.
func (s *TenantService) GetByID(ctx context.Context, tenantID uuid.UUID) (*models.Tenant, error) {
	var tenant models.Tenant
	err := s.db.GetContext(ctx, &tenant,
		`SELECT id, name, slug, settings, created_at, updated_at FROM tenants WHERE id = $1`,
		tenantID,
	)
	if err == sql.ErrNoRows {
		return nil, nil
	}
	if err != nil {
		return nil, fmt.Errorf("get tenant: %w", err)
	}
	return &tenant, nil
}

// GetUserRole returns the user's role in a tenant, or empty string if not a member.
func (s *TenantService) GetUserRole(ctx context.Context, userID, tenantID uuid.UUID) (string, error) {
	var role string
	err := s.db.GetContext(ctx, &role,
		`SELECT role FROM user_tenants WHERE user_id = $1 AND tenant_id = $2`,
		userID, tenantID,
	)
	if err == sql.ErrNoRows {
		return "", nil
	}
	if err != nil {
		return "", fmt.Errorf("get user role: %w", err)
	}
	return role, nil
}

// VerifyAccess checks if a user has access to a given tenant.
func (s *TenantService) VerifyAccess(ctx context.Context, userID, tenantID uuid.UUID) (bool, error) {
	var exists bool
	err := s.db.GetContext(ctx, &exists,
		`SELECT EXISTS(SELECT 1 FROM user_tenants WHERE user_id = $1 AND tenant_id = $2)`,
		userID, tenantID,
	)
	if err != nil {
		return false, fmt.Errorf("verify tenant access: %w", err)
	}
	return exists, nil
}

// FirstTenantForUser returns the user's first tenant (by name), used as default.
func (s *TenantService) FirstTenantForUser(ctx context.Context, userID uuid.UUID) (*uuid.UUID, error) {
	var tenantID uuid.UUID
	err := s.db.GetContext(ctx, &tenantID,
		`SELECT t.id FROM tenants t
		 JOIN user_tenants ut ON ut.tenant_id = t.id
		 WHERE ut.user_id = $1
		 ORDER BY t.name LIMIT 1`,
		userID,
	)
	if err == sql.ErrNoRows {
		return nil, nil
	}
	if err != nil {
		return nil, fmt.Errorf("first tenant: %w", err)
	}
	return &tenantID, nil
}

// ListMembers returns all members of a tenant.
func (s *TenantService) ListMembers(ctx context.Context, tenantID uuid.UUID) ([]models.UserTenant, error) {
	var members []models.UserTenant
	err := s.db.SelectContext(ctx, &members,
		`SELECT ut.user_id, ut.tenant_id, ut.role, ut.created_at, COALESCE(au.email, '') as email
		 FROM user_tenants ut
		 LEFT JOIN auth.users au ON au.id = ut.user_id
		 WHERE ut.tenant_id = $1
		 ORDER BY ut.created_at`,
		tenantID,
	)
	if err != nil {
		return nil, fmt.Errorf("list members: %w", err)
	}
	return members, nil
}

// InviteByEmail looks up a user by email in auth.users and adds them to the tenant.
func (s *TenantService) InviteByEmail(ctx context.Context, tenantID uuid.UUID, email, role string) (*models.UserTenant, error) {
	// Look up user in Supabase auth.users
	var userID uuid.UUID
	err := s.db.GetContext(ctx, &userID,
		`SELECT id FROM auth.users WHERE email = $1`,
		email,
	)
	if err == sql.ErrNoRows {
		return nil, fmt.Errorf("no user found with email %s", email)
	}
	if err != nil {
		return nil, fmt.Errorf("lookup user: %w", err)
	}

	// Check if already a member
	var exists bool
	err = s.db.GetContext(ctx, &exists,
		`SELECT EXISTS(SELECT 1 FROM user_tenants WHERE user_id = $1 AND tenant_id = $2)`,
		userID, tenantID,
	)
	if err != nil {
		return nil, fmt.Errorf("check membership: %w", err)
	}
	if exists {
		return nil, fmt.Errorf("user is already a member of this tenant")
	}

	var ut models.UserTenant
	err = s.db.QueryRowxContext(ctx,
		`INSERT INTO user_tenants (user_id, tenant_id, role) VALUES ($1, $2, $3)
		 RETURNING user_id, tenant_id, role, created_at`,
		userID, tenantID, role,
	).StructScan(&ut)
	if err != nil {
		return nil, fmt.Errorf("invite user: %w", err)
	}

	s.audit.Log(ctx, "create", "membership", &tenantID, nil, ut)
	return &ut, nil
}

// UpdateSettings merges new settings into the tenant's existing settings JSONB.
func (s *TenantService) UpdateSettings(ctx context.Context, tenantID uuid.UUID, settings json.RawMessage) (*models.Tenant, error) {
	var tenant models.Tenant
	err := s.db.QueryRowxContext(ctx,
		`UPDATE tenants SET settings = COALESCE(settings, '{}'::jsonb) || $1::jsonb, updated_at = NOW()
		 WHERE id = $2
		 RETURNING id, name, slug, settings, created_at, updated_at`,
		settings, tenantID,
	).StructScan(&tenant)
	if err != nil {
		return nil, fmt.Errorf("update settings: %w", err)
	}
	s.audit.Log(ctx, "update", "settings", &tenantID, nil, settings)
	return &tenant, nil
}

// UpdateMemberRole changes a member's role in a tenant.
func (s *TenantService) UpdateMemberRole(ctx context.Context, tenantID, userID uuid.UUID, newRole string) error {
	// Get current role
	currentRole, err := s.GetUserRole(ctx, userID, tenantID)
	if err != nil {
		return fmt.Errorf("get current role: %w", err)
	}
	if currentRole == "" {
		return fmt.Errorf("user is not a member of this tenant")
	}

	// If demoting the last owner, block it
	if currentRole == "owner" && newRole != "owner" {
		var ownerCount int
		err := s.db.GetContext(ctx, &ownerCount,
			`SELECT COUNT(*) FROM user_tenants WHERE tenant_id = $1 AND role = 'owner'`,
			tenantID)
		if err != nil {
			return fmt.Errorf("count owners: %w", err)
		}
		if ownerCount <= 1 {
			return fmt.Errorf("cannot demote the last owner")
		}
	}

	_, err = s.db.ExecContext(ctx,
		`UPDATE user_tenants SET role = $1 WHERE user_id = $2 AND tenant_id = $3`,
		newRole, userID, tenantID)
	if err != nil {
		return fmt.Errorf("update role: %w", err)
	}
	return nil
}

// AutoAssignByDomain finds a tenant with a matching auto_assign_domains setting
// and adds the user as a member. Returns the tenant and role, or nil if no match.
func (s *TenantService) AutoAssignByDomain(ctx context.Context, userID uuid.UUID, emailDomain string) (*models.TenantWithRole, error) {
	// Find tenant where settings.auto_assign_domains contains this domain
	var tenant models.Tenant
	err := s.db.GetContext(ctx, &tenant,
		`SELECT id, name, slug, settings, created_at, updated_at
		 FROM tenants
		 WHERE settings->'auto_assign_domains' ? $1
		 LIMIT 1`,
		emailDomain,
	)
	if err != nil {
		return nil, nil // no match — not an error
	}

	// Check if already a member
	var exists bool
	err = s.db.GetContext(ctx, &exists,
		`SELECT EXISTS(SELECT 1 FROM user_tenants WHERE user_id = $1 AND tenant_id = $2)`,
		userID, tenant.ID,
	)
	if err != nil {
		return nil, fmt.Errorf("check membership: %w", err)
	}
	if exists {
		// Already a member — return the existing membership
		role, err := s.GetUserRole(ctx, userID, tenant.ID)
		if err != nil {
			return nil, fmt.Errorf("get existing role: %w", err)
		}
		return &models.TenantWithRole{Tenant: tenant, Role: role}, nil
	}

	// Add as member (associate by default for auto-assigned users)
	role := "associate"
	_, err = s.db.ExecContext(ctx,
		`INSERT INTO user_tenants (user_id, tenant_id, role) VALUES ($1, $2, $3)`,
		userID, tenant.ID, role,
	)
	if err != nil {
		return nil, fmt.Errorf("auto-assign user: %w", err)
	}

	s.audit.Log(ctx, "create", "auto_membership", &tenant.ID, map[string]any{"domain": emailDomain}, map[string]any{"user_id": userID, "role": role})
	return &models.TenantWithRole{Tenant: tenant, Role: role}, nil
}

// RemoveMember removes a user from a tenant. Cannot remove the last owner.
func (s *TenantService) RemoveMember(ctx context.Context, tenantID, userID uuid.UUID) error {
	// Check if the user being removed is an owner
	role, err := s.GetUserRole(ctx, userID, tenantID)
	if err != nil {
		return fmt.Errorf("check role: %w", err)
	}
	if role == "" {
		return fmt.Errorf("user is not a member of this tenant")
	}

	if role == "owner" {
		// Count owners — prevent removing the last one
		var ownerCount int
		err := s.db.GetContext(ctx, &ownerCount,
			`SELECT COUNT(*) FROM user_tenants WHERE tenant_id = $1 AND role = 'owner'`,
			tenantID,
		)
		if err != nil {
			return fmt.Errorf("count owners: %w", err)
		}
		if ownerCount <= 1 {
			return fmt.Errorf("cannot remove the last owner of a tenant")
		}
	}

	_, err = s.db.ExecContext(ctx,
		`DELETE FROM user_tenants WHERE user_id = $1 AND tenant_id = $2`,
		userID, tenantID,
	)
	if err != nil {
		return fmt.Errorf("remove member: %w", err)
	}

	s.audit.Log(ctx, "delete", "membership", &tenantID, map[string]any{"user_id": userID, "role": role}, nil)
	return nil
}
